【问题标题】:Easy creation of properties that support indexing in C#在 C# 中轻松创建支持索引的属性
【发布时间】:2010-07-27 14:24:51
【问题描述】:

在 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”。

【问题讨论】:

标签: c# properties indexed-properties


【解决方案1】:

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 在野外

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

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

【讨论】:

  • 您在属性 getter 中使用重复的 new IndexedProperty 不必要地推动 GC。此外,委托的使用可能会阻止一系列优化。在某些用例中两者都可以接受,但总的来说我不推荐这样的代码。编写专门的索引器类肯定需要更多的工作,但性能会更好。
  • 您的示例令人困惑,因为您访问了一个集合,而无需自定义索引器类就可以访问其项目。最好使用class CExample { private string[] m_Texts; private string GetText(int index) { return m_Texts[index];} 来演示常见用法。
  • 你只是重复问题中的内容,你会让人们失去他们的时间。另外,您说这不是使用 Stack Overflow 的正确方法,但您没有说明原因。你的评论完全没用,我想了解你为什么这么说?
  • 我投票太快了(抱歉)。如果你不改变你的答案,我就不能改变我的投票。我想知道您是否可以修改它(非常轻微的修改)以便我投票赞成或反对?我可以看到它可能很有用......请注意,关于错误使用堆栈溢出而没有解释的评论真的让我感到震惊,因为它不会给任何人带来任何有用的东西......
  • 我希望你能继续走这条路,尽管它在确切的 stackoverflow 规则中并不完美......因为它可以帮助人们。虽然我现在没有使用您的代码,但我在我的通用库中保留了一份副本以备将来使用。谢谢!
【解决方案2】:

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

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

【讨论】:

  • 嗯...我喜欢这种思路。但是,我对 IList 的问题是“添加”、“删除”等成员。将属性公开为 IList 只是忽略它们是不诚实的。也许还有另一个更有意义的界面?
  • 您可以拥有一个只读的IListArrayReadOnlyCollection&lt;T&gt; 都实现了IList;当您调用AddRemove 等时,这两个类都会抛出异常。
  • 但它不是“只读”IList。您可以在特定索引处设置元素。它显然不是一个列表,但数组也不是。但是,我不知道“Array”实现了“IList”!这显然是一个糟糕的设计决定。当然,它现在会被归类为“惯用的 C#”。我在围栏上。
  • 我一直认为框架中应该有一个专门针对这种不暗示集合语义的用例的接口——IIndexable&lt;T&gt;,也许吧?尽管IList 专门为您提供了一个输出(通过IsReadOnly 属性),但它确实仅通过继承ICollection 来暗示集合语义。
  • 如果集合不应该被外界改变,使用IEnumerable。如果集合将被外界更改,请使用 ICollection。如果需要索引访问,请使用 IList.
【解决方案3】:

我认为您发布的设计是可行的方法,唯一的区别是我会定义一个界面:

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

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

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      为什么不让你的类继承 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:类似于第一篇文章

      你为什么不在你的类中有一个隐藏的列表对象,然后创建你自己的方法来获取数据。这样就看不到 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;
                  }
              }
          }
      

      【讨论】:

      • 我希望我的类型表明预期的用途。使用 IList 的问题在于,我需要为我的代码的任何使用者添加文档,说是的,我实现了 IList,但你不应该使用“添加”或“删除”。我更喜欢自我记录的代码。
      • @cdiggins 我对此有两种看法。我原则上同意你的观点,但是对于IListICollection,框架文档特别指出,实现类应该在调用AddClearInsertRemove 时抛出NotSupportedException , 或 RemoveAt 方法,如果集合是只读的(即,如果 IsReadOnly 属性返回 true)。期望您的用户以符合文档的方式使用您的代码是合理的,因此我认为使用 IList 是合理的 - 实际上是做您想做的事情的推荐方式。
      • 嘿 Gage,我已经在我的帖子中链接到那篇 MSDN 文章。
      【解决方案6】:

      经过一些研究,我想出了一个稍微不同的解决方案,更能满足我的需求。这个例子有点虚构,但它确实适合我需要它来适应它。

      用法:

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

      【讨论】:

      • 这就是我要找的。紧凑型。
      猜你喜欢
      • 1970-01-01
      • 2013-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      • 2019-09-15
      • 1970-01-01
      • 2018-03-14
      相关资源
      最近更新 更多