轻松创建支持C#索引的属性

Easy creation of properties that support indexing in C#

在C中,我发现索引属性非常有用。例如:

1
2
3
var myObj = new MyClass();
myObj[42] ="hello";
Console.WriteLine(myObj[42]);

然而,据我所知,没有任何句法上的制糖来支持自己支持索引的字段(如果我错了,请纠正我)。例如:

1
2
3
var myObj = new MyClass();
myObj.field[42] ="hello";
Console.WriteLine(myObj.field[42]);

我需要这样做的原因是我已经在类上使用了index属性,但是我有GetNumX()GetX()SetX()函数,如下所示:

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语言中是否有更惯用的方法来创建一个可索引的字段属性,如果没有,如何改进我的IndexedProperty类?

编辑:经过进一步研究,乔恩·斯基特称之为"命名索引器"。


从技术上讲,这不是使用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的对象。

记住,仅仅因为它实现了IList并不意味着它本身就是一个集合,只是它实现了某些方法。


我认为你发布的设计是一种可行的方法,我将定义一个接口的一个区别是:

1
2
3
4
public interface IIndexed<IndexT, ValueT>
{
    ValueT this[IndexT i] { get; set; }
}

对于常见情况,我将使用您在原始问题中放入的类(它将实现这个接口)。

如果基类库为我们提供了一个合适的接口,那就太好了,但事实并非如此。


这并不能回答您的问题,但有趣的是,CIL支持创建您所描述的属性——某些语言(例如f)也允许您以这种方式定义它们。

c中的this[]索引器只是其中一个的特定实例,当您构建应用程序时,它将重命名为Item。C编译器只知道如何读取此属性,因此,如果您在F库中编写一个名为Target的"命名索引器",并尝试在C中使用它,则可以访问该属性的唯一方法是通过... get_Target(int)void set_Target(int, ...)方法。烂透了。


为什么不让您的类继承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
MyClass MC = new MyClass();
int x = MC.IntProperty[5];

以及使其工作的代码:

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中的命名索引属性