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

时间:2010-07-27 14:24:51

标签: c# properties indexed-properties

在C#中,我发现indexed properties非常有用。例如:

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

但据我所知,没有语法糖来支持自己支持索引的字段(如果我错了请纠正我)。例如:

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

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

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);
}

正如您可能会看到将这些作为一个可索引字段属性公开会更有意义。我可以编写一个自定义类来实现这一点,每次我想要语法糖,但所有的样板代码似乎都没有用。

因此,我编写了一个自定义类来封装样板,并且可以轻松创建可以编制索引的属性。这样我可以按如下方式添加新属性:

public IndexedProperty<ReferenceTarget> TargetArray  {
    get { 
       return new IndexedProperty<int, ReferenceTarget>(
           (int n) => GetTarget(n), 
           (int n, ReferenceTarget rt) => SetTarget(n, rt));
       }
}

这个新的IndexedProperty类的代码如下所示:

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类?

编辑:经过进一步研究,Jon Skeet称之为“named indexer”。

7 个答案:

答案 0 :(得分:9)

这在技术上并不是使用StackOverflow的正确方法,但我发现你的想法非常有用,我对它进行了扩展。如果我发布了我想出的内容以及如何使用它的示例,我认为这会节省人们的时间。

首先,我需要能够支持get-only和set-only属性,所以我为这些场景略微改变了代码:

获取并设置(非常小的更改):

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);
        }
    }
}

仅限获取:

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);
        }
    }
}

仅限设置:

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);
        }
    }
}

示例

这是一个简单的用法示例。我继承了Collection并创建了一个命名索引器,就像Jon Skeet所说的那样。这个例子很简单,不实用:

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;
    }
}

Wild中的ExampleCollection

这个仓促构建的单元测试显示了在项目中的ExampleCollection时它的外观:

[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");

    }
}

最后,如果您想使用仅限get和only-only版本,则如下所示:

    public ReadOnlyIndexedProperty<int, T> ExampleProperty
    {
        get
        {
            return new ReadOnlyIndexedProperty<int, T>(GetIndex);
        }
    }

或者:

    public WriteOnlyIndexedProperty<int, T> ExampleProperty
    {
        get
        {
            return new WriteOnlyIndexedProperty<int, T>(SetIndex);
        }
    }

在这两种情况下,结果都与您期望只使用get / only-only属性的行为完全相同。

答案 1 :(得分:8)

嗯,最简单的方法是让属性返回一个实现IList的对象。

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

答案 2 :(得分:7)

我认为你发布的设计是要走的路,我将定义一个界面的一个不同之处:

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

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

如果基类库为我们提供了合适的接口会很好,但事实并非如此。在这里返回IList将是一种变态。

答案 3 :(得分:4)

这不能回答你的问题,但有趣的是要注意CIL支持制作你所描述的属性 - 某些语言(例如,F#)也允许你以这种方式定义它们。

C#中的this[]索引器只是其中一个的特定实例,在您构建应用时会重命名为Item。 C#编译器只知道如何读取这个,所以如果你在F#库中编写一个名为Target的“命名索引器”,并尝试在C#中使用它,你可以访问该属性的唯一方法是通过... get_Target(int)void set_Target(int, ...)方法。吸。

答案 4 :(得分:3)

为什么不让你的类继承IList然后你可以使用索引并为它添加自己的属性。虽然您仍然可以使用添加和删除功能,但不要使用它们。另外,你可能会觉得让它们继续发展是有用的。

有关列表和数组的更多信息,请查看: Which is better to use array or List<>?

修改

MSDN上有一篇关于您可能想要查看的索引属性的文章。似乎并不复杂,只是乏味。

http://msdn.microsoft.com/en-us/library/aa288464(VS.71).aspx

还有另一个选项可以创建替代的Add方法,但根据对象的类型,可能无法始终调用add方法。在这里解释:

How do I override List<T>'s Add method in C#?

编辑2:与第一篇文章类似

为什么你的类中没有隐藏的列表对象,然后只是创建自己的方法来获取数据。这样就看不到添加和删除,列表已经编入索引。

你是什么意思“命名索引器”是你在寻找行[“My_Column_Name”]的等价物。我发现这篇MSDN文章可能很有用,因为它似乎显示了实现该属性的基本方法。

http://msdn.microsoft.com/en-us/library/146h6tk5.aspx

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;
            }
        }
    }

答案 5 :(得分:2)

经过一番研究,我想出了一个稍微不同的解决方案,更符合我的需求。这个例子有点炮制,但它确实适合我需要它来适应它。

用法:

MyClass MC = new MyClass();
int x = MC.IntProperty[5];

使代码工作的代码:

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); }
        }
    }
}

答案 6 :(得分:0)

尝试显式实现的接口,如回复中提出的第二种方式所示: Named indexed property in C#?