【问题标题】:Indexer get by reference or get+set?索引器通过引用获取还是获取+设置?
【发布时间】:2020-02-28 02:41:07
【问题描述】:

这是我的课:

class MyArray<T>
{
    private T[] data;
    public MyArray(int size)
    {
        data = new T[size];
    }
    public MyArray(in T[] array)
    {
        data = new T[array.Length];
        for (int i = 0; i < array.Length; i++)
            data[i] = array[i];
    }
    public T this[int i] //I am talking about this indexer
    {
        get => data[i];
        set => data[i] = value;
    }
}

我是否应该像这样定义我的索引器,而不是像上面那样做?

public ref T this[int i]
{
    get => data[i];
}

我应该选择哪种方法?据我所知,方法 1 允许您在设置值时执行其他代码,但是方法 2 允许您执行类似 int.TryParse(str, out myArrayObj[i]);

的操作

编辑:这个问题不仅适用于索引器,而且一般适用于任何属性。

【问题讨论】:

  • 我认为您已经回答了自己的问题。这两种方法支持不同的功能,哪一种对您来说更重要?
  • 您应该选择最适合您需要的方法。
  • @Sach 好吧,List&lt;T&gt; 不会通过引用返回,但是在设置元素时它不会执行任何额外的代码。
  • @DarkAtom 我认为List&lt;T&gt; 是在C# 返回ref 之前创建的,所以今天可能会有所不同。当然有理由(自动激活、模拟数组)希望它这样做。
  • 数据类型(此处为int)是原始类型还是引用类型有区别吗?

标签: c# c#-7.0


【解决方案1】:

TL;DR 如果您需要一个设置器,那么您必须使用常规索引器,因为ref 属性不能有设置器。如果不需要 setter,那么我将始终在 generic 类型参数的 indexer/property 中指定 ref 关键字,因为它们可以是值类型。是否通过 ref 访问它们取决于消费者。

ref 关键字在类型参数是引用类型(类)的情况下没有任何好处,但对值类型有影响,因为它们可以直接访问而无需复制。因此,如果您想访问 your 数组中的原始值,则必须使用 ref 关键字。如果要访问数组中的值,该值已传递给MyArray&lt;T&gt; 类,则不应将值复制到其他私有数组,因为在这种情况下,您将获得私有数组中值的副本。

以下示例显示了通过 ref 访问与常规访问的不同之处:

[Fact]
public void ArrayTest()
{
    var structs = new[] {new MyStruct()};

    structs[0].Type++;

    ref var @struct = ref structs[0];
    @struct.Type++;

    // Test passes the value was incremented twice, because reference was used.
    structs[0].Type.Should().Be(2);
}

[Fact]
public void MyArrayTest()
{
    var structs = new MyArray<MyStruct>(new[] {new MyStruct()});

    // Compiler error, but was possible in earlier versions of C#
    // and would not modify the item in the array,
    // because value was copied before modification.
    // structs[0].Type++;

    var @struct = structs[0];
    @struct.Type++;

    // Value wasn't incremented, because it was copied on the stack.
    structs[0].Type.Should().Be(0);
}

[Fact]
public void MyRefArrayTest()
{
    var structs = new MyRefArray<MyStruct>(new[] {new MyStruct()});

    structs[0].Type++;

    ref var @struct = ref structs[0];
    @struct.Type++;

    // Test passes the value was incremented twice, because reference was used.
    structs[0].Type.Should().Be(2);
}

struct MyStruct
{
    public int Type { get; set; }
}

class MyArray<T>
{
    private T[] data;

    public MyArray(in T[] array)
    {
        data = new T[array.Length];
        Array.Copy(array, data, array.Length);
    }

    public T this[int i]
    {
        get => data[i];
        set => data[i] = value;
    }
}

class MyRefArray<T>
{
    private T[] data;

    public MyRefArray(in T[] array)
    {
        data = new T[array.Length];
        Array.Copy(array, data, array.Length);
    }

    public ref T this[int i]
    {
        get => ref data[i];

        // Compiler error, ref indexer cannot have setter.
        // set => data[i] = value;
    }
}

【讨论】:

    【解决方案2】:

    这很大程度上取决于你打算用它做什么,但我会从封装方面看到它:

    经典的getset 方法是坚固且健壮:使用您的对象的每个人在获取或设置对象中的任何值时都将遵循您的规则。

    ref 情况变得棘手!您只是给出参考,对象的用户可以用它做任何他/她想做的事情。这就像赠送您的家庭钥匙一样。

    您无法保护自己免受用户篡改您的私人成员:

    // This might cause several failures or go against the class contract 
    // and there is no way to check on the value.
    public void AbuseRefProperty() => array.Property = null; 
    

    唯一让我觉得拥有 ref 属性很舒服的地方是在私有内部类中。在那里你确定没有人可以篡改它,并且父类是唯一拥有完全控制权的类。

    【讨论】:

    • 如果我的类不拥有数据,但它只是代理/包装器。我希望它尽可能薄,因此ref 似乎是第一解决方案。
    • 在极少数情况下不可能误用,ref 绝对是一个不错的选择!但是,它确实总是违反封装原则。
    猜你喜欢
    • 2021-01-17
    • 1970-01-01
    • 2013-03-05
    • 2012-10-13
    • 1970-01-01
    • 2016-10-23
    • 2014-09-02
    • 2012-09-19
    • 2020-08-03
    相关资源
    最近更新 更多