C#中的命名索引属性

本文关键字:索引 属性 | 更新日期: 2023-09-27 17:58:35

一些语言(如Delphi)有一种非常方便的创建索引器的方法:不仅可以对整个类进行索引,甚至可以对单个属性进行索引,例如:

type TMyClass = class(TObject)
protected
    function GetMyProp(index : integer) : string;
    procedure SetMyProp(index : integer; value : string);
public
    property MyProp[index : integer] : string read GetMyProp write SetMyProp;
end;

这可以很容易地使用:

var c : TMyClass;
begin
    c = TMyClass.Create;
    c.MyProp[5] := 'Ala ma kota';
    c.Free;
end;

有没有一种方法可以在C#中轻松实现同样的效果?

C#中的命名索引属性

众所周知的解决方案是创建一个代理类:

public class MyClass
{
    public class MyPropProxy
    {
        private MyClass c;
        // ctor etc.
        public string this[int index]
        {
            get
            {
                return c.list[index];
            }
            set
            {
                c.list[index] = value;
            }
        }
    }
    private List<string> list;
    private MyPropProxy myPropProxy;
    // ctor etc.
    public MyPropProxy MyProp
    { 
        get
        {
            return myPropProxy;
        }
    }
}

但是(例外的是,这实际上解决了问题),这个解决方案主要只引入了缺点:

  • 它会导致代码被(可能)许多小型代理类污染
  • 所提出的解决方案稍微破坏了封装(内部类访问外部类的私有成员),更好的解决方案是将列表的实例传递给MyPropProxy的ctor,这将需要更多的代码
  • 我不建议公开内部助手类。可以通过引入额外的接口来解决这个问题,但这甚至是一个需要实现的实体(测试、维护等)

不过,还有另一种方法。它对代码的污染也比前一个有所减少:

public interface IMyProp
{
    string this[int index] { get; }
}
public class MyClass : IMyProp
{
    private List<string> list;
    string IMyProp.this[int index]
    {
        get
        {
            return list[index];
        }
        set
        {
            list[index] = value;
        }
    }
    // ctor etc.
    public IMyProp MyProp 
    {
        get
        {
            return this;
        }
    }
}

优点:

  • 没有代理类(它们占用内存中的空间,仅用于单一目的,并且(在最简单的解决方案中)破坏封装
  • 简单的解决方案,只需少量代码即可添加另一个索引属性

缺点:

  • 每个属性都需要不同的公共接口
  • 随着索引属性的增加,类实现了越来越多的接口

这是向C#引入索引属性的最简单的方法(就代码长度和复杂性而言)。当然,除非有人发布更短、更简单的帖子。

这是基于H B的注释,但扩展了一点,并作为一个代码块(使复制更容易):

public interface  IIndexedProperty<TValue> : IIndexedProperty<int, TValue> {}
public interface IReadOnlyIndexedProperty<out TValue> : IReadOnlyIndexedProperty<int, TValue> {}
public interface IIndexedProperty<in TIndex, TValue>
{
    TValue this[TIndex index] { get; set; }
}
public interface IReadOnlyIndexedProperty<in TIndex, out TValue>
{
    TValue this[TIndex index] { get; }
}

这使用了C#9.0中的共变量,所以如果您使用的是旧版本,请去掉inout语句。

优点:大多数常见的索引属性使用简单的int索引,因此,如果索引只需要是int,则可以使用更简单的类/属性签名,但也可以使用非int索引。

还根据您的需要提供读/写实现和只读实现。

我在尝试使用后发现:int仅索引类签名的快捷方式。显然,它仍然需要该房产的完整签名。

IE:

public MyClass : IIndexedProperty<string>
{
    public IIndexedProperty<string> MyString => this;
    string IIndexedPropety<int, string>.this[int index] { get; set; }
}