34

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

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 将其称为“命名索引器”。

4

6 回答 6

17

2022 年编辑:这将继续获得投票,但它可能不是我今天会使用的东西,主要是因为它确实以一种不理想的方式推动垃圾收集,如果该属性受到很多打击的话。我记得这是一个复杂的话题,我现在不想深入研究它,但我想知道索引器今天是否可以解决这个问题。请参阅:https ://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/indexers/


我发现你的想法很有用,所以我扩展了它。这在技术上可能不是一个正确的答案,因为我不确定它是否能直接回答您的问题,但我认为它可能对来这里寻找房产索引器的人有用。

首先,我需要能够支持 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;
    }
}

ExampleCollection in the Wild

这个仓促构建的单元测试显示了当您在项目中使用 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 和 set-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);
        }
    }

在这两种情况下,结果的工作方式都与您期望的仅获取/仅设置属性的行为方式相同。

于 2013-07-12T15:46:17.503 回答
9

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

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

于 2010-07-27T14:30:40.903 回答
7

我认为您发布的设计是要走的路,唯一的区别是我会定义一个界面:

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

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

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

于 2010-07-27T18:00:18.087 回答
5

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

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

于 2010-07-27T18:16:52.447 回答
3

为什么不让你的类继承 IList 然后你可以使用索引并向它添加你自己的属性。尽管您仍然可以使用添加和删除功能,但不使用它们并不是不诚实的。另外,您可能会发现让他们在路上继续前进很有用。

有关列表和数组的更多信息,请查看: 使用数组或 List<> 哪个更好?

编辑:

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

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

还有另一种选择,您可以创建替代的 Add 方法,但根据对象的类型,您的 add 方法可能并不总是被调用。在这里解释:

如何在 C# 中覆盖 List<T> 的 Add 方法?

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

你为什么不在你的类中有一个隐藏的列表对象,然后创建你自己的方法来获取数据。这样就看不到 Add 和 Remove 了,并且列表已经被索引了。

另外,“命名索引器”是什么意思,您是否正在寻找相当于行 [“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;
            }
        }
    }
于 2010-07-27T14:42:00.030 回答
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); }
        }
    }
}
于 2015-02-24T13:18:58.877 回答