为什么“in”关键字允许 C# 属性的突变?

Why does the "in" keyword allow mutation of properties C#?

提问人:Patrick Cerny 提问时间:7/24/2023 最后编辑:CharliefacePatrick Cerny 更新时间:7/24/2023 访问量:147

问:

我目前正在研究一个函数,其中传递了一个对象(自写类型),并且不得在被调用的函数中发生突变/更改。这里有一个简单的例子。Catalog


public override bool Migrate(in Catalog fromCatalog)
{
    fromCatalog.SupplierId = 1; //works, but why
}

我想严格破坏这种行为,并不允许改变传递对象的任何属性

C# .NET 按引用传递

评论

1赞 Selvin 7/24/2023
然后使属性只读...为什么“in”不应该允许突变?它仅适用于参数本身,而不适用于其成员SupplierId
1赞 Mathias R. Jessen 7/24/2023
in只是表示编译器阻止了,而不是fromCatalog = ...fromCatalog.Member = ...
0赞 Patrick Cerny 7/24/2023
好吧,这是有道理的。但是,如何在不直接指定任何属性的情况下破坏任何属性的变异。readonly
0赞 Selvin 7/24/2023
由 usnig ?readonly record struct
1赞 Andrew Williamson 7/24/2023
如果您使用的是结构,它们几乎总是应该作为不可变类型实现。如果 Catalog 是一个类,并且您仍然希望所有者能够更改它,则创建一个接口“IReadOnlyCatalog”,该接口具有所有属性的公共 getter,并且没有 setter。您可以在“IList”和“IReadOnlyList”中看到类似的东西

答:

2赞 Volodymyr 7/24/2023 #1

因为 in 修饰符仅适用于变量(在本例中为参数)。它可以防止参数更改其值以引用其他对象。

public bool Migrate(in Catalog fromCatalog)
{
/* Error CS8331  Cannot assign to variable 'fromCatalog' 
or use it as the right hand side of a ref assignment 
because it is a readonly variable */
    fromCatalog = new Catalog(); 

    fromCatalog.SupplierId = 1;
}

并创建对象的浅拷贝,以便您不会更改传递的对象(在类中)中的任何内容

例:

public Catalog CreateACopy()
{
    Catalog obj = (Catalog)this.MemberwiseClone();
    // and other...
    return obj;
}
        

利用率

public class Program 
{
    private static void Main()
    {
        Catalog catalog = new Catalog();
        new Program().Migrate(catalog.CreateACopy());
    }
    public bool Migrate(in Catalog fromCatalog)
    {
        fromCatalog.SupplierId = 1;
    }
}

评论

3赞 Jon Skeet 7/24/2023
“因为 in 修饰符仅适用于对象” - 不,它适用于参数,而不是对象。
0赞 Volodymyr 7/24/2023
我想说 in 修饰符不允许传递的变量更改此方法中的引用,对吗?
3赞 Jon Skeet 7/24/2023
有点,虽然我不是这样表达的。但是,了解变量(在本例中为参数)和对象之间的区别非常重要。如果修饰符真正应用于对象,它将阻止对对象本身进行更改,而这正是它做的。它可以防止参数更改其值以引用其他对象。in
1赞 Andrew Williamson 7/24/2023
再加上 Jon 的评论,编译器很难准确地阻止对对象本身进行更改。禁止直接成员赋值很容易,但编译器还必须有一种方法来区分哪些方法会改变对象,以便防止它们被调用。这并不像“任何改变对象的方法”那么简单,因为某些方法可能只是填充缓存
1赞 JonasH 7/24/2023 #2

它之所以有效,是因为 Catalog 具有可写属性。C# 没有常量值的概念,例如 C++。所以只是另一个值,你可以传递,调用方法,设置属性等。fromCatalog

但这并不意味着编写这样的代码没有多大意义。关键字最有用的是避免值类型的副本。但是,如果值类型是可变的,编译器无论如何都必须创建一个防御性副本,只是为了防止原始对象被修改。in

因此,一般建议是使您的值类型不可变/只读,这样可以避免该问题。另请参阅为什么可变结构是邪恶的