Easy creation of properties that support indexing in C#
在C中,我发现索引属性非常有用。例如:
1 2 3 |
然而,据我所知,没有任何句法上的制糖来支持自己支持索引的字段(如果我错了,请纠正我)。例如:
1 2 3 |
号
我需要这样做的原因是我已经在类上使用了index属性,但是我有
1 2 3 4 5 6 7 8 9 | public int NumTargetSlots { get { return _Maker.NumRefs; } } public ReferenceTarget GetTarget(int n) { return ReferenceTarget.Create(_Maker.GetReference(n)); } public void SetTarget(int n, ReferenceTarget rt) { _Maker.ReplaceReference(n, rt._Target, true); } |
正如您可能看到的,将这些属性公开为一个可索引字段属性将更有意义。我可以编写一个定制类来实现这一点,每次我想要语法上的糖分,但所有样板代码似乎都是不必要的。
因此,我编写了一个自定义类来封装样板文件,并使创建可索引的属性变得容易。这样我可以添加一个新属性,如下所示:
1 2 3 4 5 6 7 | public IndexedProperty<ReferenceTarget> TargetArray { get { return new IndexedProperty<int, ReferenceTarget>( (int n) => GetTarget(n), (int n, ReferenceTarget rt) => SetTarget(n, rt)); } } |
。
此新indexedproperty类的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class IndexedProperty<IndexT, ValueT> { Action<IndexT, ValueT> setAction; Func<IndexT, ValueT> getFunc; public IndexedProperty(Func<IndexT, ValueT> getFunc, Action<IndexT, ValueT> setAction) { this.getFunc = getFunc; this.setAction = setAction; } public ValueT this[IndexT i] { get { return getFunc(i); } set { setAction(i, value); } } } |
所以我的问题是:有没有更好的方法来完成所有这些工作?
具体来说,C语言中是否有更惯用的方法来创建一个可索引的字段属性,如果没有,如何改进我的
编辑:经过进一步研究,乔恩·斯基特称之为"命名索引器"。
从技术上讲,这不是使用stackoverflow的正确方法,但我发现您的想法非常有用,所以我扩展了它。我想,如果我把我的想法和一个如何使用它的例子发布出来,它会节省人们的时间。
首先,我需要能够支持get-only和set-only属性,因此我对这些场景中的代码做了一些细微的更改:
获取并设置(非常小的更改):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class IndexedProperty<TIndex, TValue> { readonly Action<TIndex, TValue> SetAction; readonly Func<TIndex, TValue> GetFunc; public IndexedProperty(Func<TIndex, TValue> getFunc, Action<TIndex, TValue> setAction) { this.GetFunc = getFunc; this.SetAction = setAction; } public TValue this[TIndex i] { get { return GetFunc(i); } set { SetAction(i, value); } } } |
仅获取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class ReadOnlyIndexedProperty<TIndex, TValue> { readonly Func<TIndex, TValue> GetFunc; public ReadOnlyIndexedProperty(Func<TIndex, TValue> getFunc) { this.GetFunc = getFunc; } public TValue this[TIndex i] { get { return GetFunc(i); } } } |
。
仅设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class WriteOnlyIndexedProperty<TIndex, TValue> { readonly Action<TIndex, TValue> SetAction; public WriteOnlyIndexedProperty(Action<TIndex, TValue> setAction) { this.SetAction = setAction; } public TValue this[TIndex i] { set { SetAction(i, value); } } } |
例子
下面是一个简单的用法示例。我从集合继承并创建一个命名索引器,如jon skeet所说。此示例旨在简单而不实用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class ExampleCollection<T> : Collection<T> { public IndexedProperty<int, T> ExampleProperty { get { return new IndexedProperty<int, T>(GetIndex, SetIndex); } } private T GetIndex(int index) { return this[index]; } private void SetIndex(int index, T value) { this[index] = value; } } |
。
野外采集示例
这个仓促构建的单元测试显示了在项目中的exampleCollection时的外观:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [TestClass] public class IndexPropertyTests { [TestMethod] public void IndexPropertyTest() { var MyExample = new ExampleCollection<string>(); MyExample.Add("a"); MyExample.Add("b"); Assert.IsTrue(MyExample.ExampleProperty[0] =="a"); Assert.IsTrue(MyExample.ExampleProperty[1] =="b"); MyExample.ExampleProperty[0] ="c"; Assert.IsTrue(MyExample.ExampleProperty[0] =="c"); } } |
。
最后,如果要使用"仅获取"和"仅设置"版本,则如下所示:
1 2 3 4 5 6 7 | public ReadOnlyIndexedProperty<int, T> ExampleProperty { get { return new ReadOnlyIndexedProperty<int, T>(GetIndex); } } |
或:
1 2 3 4 5 6 7 | public WriteOnlyIndexedProperty<int, T> ExampleProperty { get { return new WriteOnlyIndexedProperty<int, T>(SetIndex); } } |
。
在这两种情况下,结果的工作方式与预期的只获取/只设置属性的工作方式完全相同。
最简单的方法是让属性返回一个实现
记住,仅仅因为它实现了IList并不意味着它本身就是一个集合,只是它实现了某些方法。
我认为你发布的设计是一种可行的方法,我将定义一个接口的一个区别是:
1 2 3 4 | public interface IIndexed<IndexT, ValueT> { ValueT this[IndexT i] { get; set; } } |
号
对于常见情况,我将使用您在原始问题中放入的类(它将实现这个接口)。
如果基类库为我们提供了一个合适的接口,那就太好了,但事实并非如此。
这并不能回答您的问题,但有趣的是,CIL支持创建您所描述的属性——某些语言(例如f)也允许您以这种方式定义它们。
c中的
为什么不让您的类继承IList,那么您就可以使用索引并向它添加您自己的属性。虽然您仍然拥有添加和删除功能,但是不使用它们并不是不诚实的。另外,你可能会发现让他们继续前进很有用。
有关列表和数组的详细信息,请签出:使用数组或列表哪个更好?
编辑:
msdn有一篇关于索引属性的文章,您可能想看看。似乎并不复杂,只是乏味。
http://msdn.microsoft.com/en-us/library/aa288464(vs.71).aspx
还有另一个选项,您可以在其中创建一个可选的添加方法,但根据对象的类型,可能不会始终调用添加方法。解释如下:
如何在C中重写List的Add方法?
编辑2:类似于第一篇文章
为什么你不在你的类中有一个隐藏的列表对象,然后创建你自己的方法来获取数据呢?这样就看不到添加和删除,并且列表已经被索引。
您所说的"命名索引器"是什么意思?您在寻找行的等价物[我的列的名称]。我发现有一篇MSDN文章可能很有用,因为它似乎显示了实现该属性的基本方法。
http://msdn.microsoft.com/en-us/library/146h6tk5.aspx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Test { private List<T> index; public T this[string name]{ get; set; } public T this[int i] { get { return index[i]; } set { index[i] = value; } } } |
经过一些研究,我想出了一个稍微不同的解决方案,更好地满足我的需要。这个例子有点捏造,但它确实适合我需要它来适应的东西。
用途:
1 2 |
以及使其工作的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | public class MyClass { public readonly IntIndexing IntProperty; public MyClass() { IntProperty = new IntIndexing(this); } private int GetInt(int index) { switch (index) { case 1: return 56; case 2: return 47; case 3: return 88; case 4: return 12; case 5: return 32; default: return -1; } } public class IntIndexing { private MyClass MC; internal IntIndexing(MyClass mc) { MC = mc; } public int this[int index] { get { return MC.GetInt(index); } } } } |
。
尝试显式实现的接口,如这里回复中建议的第二种方式所示:C中的命名索引属性