索引器是通过引用获取还是获取+设置?

Indexer get by reference or get+set?

这是我的 class:

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

的操作

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

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

classic get set 方法 坚固耐用 :使用您的对象的每个人在获取或获取时都会遵循您的规则在对象中设置任何值。

有了ref,情况就变得棘手了!您只是给出引用,对象的用户可以用它做任何 he/she 想做的事。这就像放弃你的家钥匙。

您无法防止用户篡改您的私人成员:

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

唯一让我觉得有参考 属性 的地方是 在私人内部 class。在那里你确定没有人可以篡改它并且父 class 是唯一拥有完全控制权的人。

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

如果类型参数是引用类型(class),

ref 关键字不提供任何好处,但对值类型有影响,因为它们可以直接访问而无需复制。因此,如果您想访问 您的 数组中的原始值,则必须使用 ref 关键字。如果你想访问数组中传递给 MyArray<T> class 的值,那么你不应该将值复制到其他私有数组,因为在这种情况下你将获得值的副本私有数组。

以下示例显示了 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;
    }
}