标记为只读的值类型字段的异常行为

时间:2019-01-09 17:01:04

标签: c#

我与此情况类似:

struct A
{
    string[] strs;

    public A(int cap) => strs = new string[cap];

    public void Set(int i, string s) => strs[i] = s;
}

class B
{
    readonly A a = new A(5);

    public void Set(int i, string s) => a.Set(i, s);
}

并且我正在类Set上调用B方法,但是a中的数组没有改变,但是,如果我删除了readonly关键字,该方法按预期工作,并且更改了数组。

我意识到A应该是一个类而不是结构,但是我主要是想了解为什么会发生这种情况。

1 个答案:

答案 0 :(得分:4)

您要报告的内容听起来像在您的 real 代码中,实际上是将 field 设置为A(问题中的代码不起作用)以要求的方式)。例如,此代码将以这种方式失败:

class Program
{
    static void Main(string[] args)
    {
        var b = new B();
        b.Set("abc");
        // writes "init" if readonly left in, "abc" otherwise
        Console.WriteLine(b.ToString());
    }
}
struct A
{
    string _s;
    public A(int cap) => _s = "init";
    public void Set(string s) => _s = s;
    public override string ToString() => _s;
}

class B
{
    readonly A a = new A(5);
    public void Set(string s) => a.Set(s);
    public override string ToString() => a.ToString();
}

这样做的原因是,在readonly中,对a.Set()的调用实际上是:

var tmp = a;
tmp.Set(); // operates on a clone

恰恰是因为它要保证字段声明的readonly部分-否则,对Set()的调用会产生更改值的副作用只读字段。要以最简单的方式避免这种情况:避免使用可变结构!在最新的C#版本(7.2+)中,您可以将结构声明为readonly struct,这将有助于您强制执行此操作(如果尝试执行任何危险的 操作,它将无法编译,并且允许编译器删除此多余的克隆步骤,并使用in(“ ref只读”引用)修饰符的更有效实现。