C# 如何在类定义中使结构不可变

C# 如何在类定义中使结构不可变,c#,class,struct,immutability,C#,Class,Struct,Immutability,我有一个关于在类定义中创建不可变结构的问题。我想在类之外定义结构,但在类定义中使用相同的结构类型,同时保持不变性。下面的代码能否实现这一点 namespace space { class Class1 { public Struct {get; set;} } public Struct { public Struct(string strVar) { StructVar = strV

我有一个关于在类定义中创建不可变结构的问题。我想在类之外定义结构,但在类定义中使用相同的结构类型,同时保持不变性。下面的代码能否实现这一点

namespace space
{
    class Class1
    {
        public Struct {get; set;}
    }
    public Struct
    {
        public Struct(string strVar)
        {
            StructVar = strVar;
        }
        public string StructVar {get;}
    }
}
另外,如果我在结构中有一个结构,如:

class Class1
{
    public Struct2 {get; set;}
}
public struct Struct2
{
    public Struct2(string str, InStruct inStrct)
    {
        StrVar = str;
        InStruct = inStrct;
    }
    public string StrVar {get;}
    public InStruct InStruct {get;}
}
public struct InStruct
{
    public InStruct(Array ary)
    {
        StrArray = ary
    }
    public Array StrArray {get;}
}
这是否也保持了不变性

最后,如果指令中数组的大小可能很长,我是否应该根本不使用结构,而是将数组本身放入类定义中?我是不是快疯了


我担心的是,因为我在类定义中做了一个{set;},所以我在某个地方违反了规则。我会将结构放在类定义本身中,但我不想不断地调用类构造函数来创建每个结构,这种情况似乎在一开始就违背了使用结构的目的。

如果不完全理解您要实现的目标,给出完整的答案有点困难,但我将从几个重要的区别开始

首先,在C#中,结构/类的区别并不在于可变性本身。你可以有一个不可变的类,就像这个

public class CannotBeMutated
{
    private string someVal;
    public CannotBeMutated(string someVal)
    {
        _someVal = someVal
    }

    public string SomeVal => _someVal;
}
// This is not at all idiomatic C#, please don't use this as an example
public struct MutableStruct
{
      private string _someVal;
      public MutableStruct(string someVal)
      {
          _someVal = someVal;
      }

      public void GetVal()
      {
          return _someVal
      }

      public void Mutate(string newVal)
      {
          _someVal = newVal;
      }
}
还有一个可变结构,比如这个

public class CannotBeMutated
{
    private string someVal;
    public CannotBeMutated(string someVal)
    {
        _someVal = someVal
    }

    public string SomeVal => _someVal;
}
// This is not at all idiomatic C#, please don't use this as an example
public struct MutableStruct
{
      private string _someVal;
      public MutableStruct(string someVal)
      {
          _someVal = someVal;
      }

      public void GetVal()
      {
          return _someVal
      }

      public void Mutate(string newVal)
      {
          _someVal = newVal;
      }
}
使用上面的结构,我可以做到这一点

 var foo = new MutableStruct("Hello");
 foo.mutate("GoodBye");
 var bar = foo.GetVal(); // bar == "GoodBye"!
结构和类之间的区别在于变量传递语义。当值类型的对象(例如结构)被分配给变量、作为参数传递给方法或从方法(包括属性getter或setter)返回时,在将该对象传递给新函数上下文之前,将创建该对象的副本。当引用类型的对象作为参数传递给方法或从方法返回时,不会进行复制,因为我们只传递对象在内存中的位置的引用,而不是对象的副本

关于结构“复制”的另一点。假设您有一个带有引用类型字段的结构,如下所示

public struct StructWithAReferenceType
{
   public List<string> IAmAReferenceType {get; set;}
}
var foo = new Class1();
foo.Struct2 = new Struct2("a", 1);
foo.Struct2 // returns a copy of Struct2("a", 1);
foo.Struct2 = new Struct2("b", 2);
foo.Struct2 // returns a copy of Struct2("b", 2);
Struct2是不可变的,因为调用代码无法一次性更改
StrVar
InVar
的值。指令同样是不变的。然而,数组不是不变的。因此,指令是可变值的不可变容器。类似于如果您有一个
不可变列表
。虽然可以保证调用代码不会将director.StrArray的值更改为其他数组,但对于调用代码更改数组中对象的值,您无能为力

最后,给出一些与您的示例相关的一般建议

首先,可变结构或具有可变字段的结构是不好的。上面的例子应该指出为什么具有可变字段的结构是不好的。Eric Lippert自己在他的博客上有一个很好的例子,说明可变结构是多么可怕

其次,对于大多数使用C语言的开发人员来说,几乎没有理由创建用户定义的值类型(即结构)。值类型的对象存储在堆栈上,这使得对它们的内存访问非常快。引用类型的对象存储在堆上,因此访问速度较慢。但是,在绝大多数C#程序中,磁盘I/O、网络I/O、序列化代码中的反射,甚至集合的初始化和操作的时间成本将使这种区别相形见绌。对于不编写性能关键标准库的普通开发人员来说,几乎没有理由考虑这种差异对性能的影响。见鬼,Java、Python、Ruby、Javascript和许多其他语言的开发人员完全不用用户定义的值类型就能在语言中生存。通常,它们为开发人员带来的额外认知开销几乎不值得您看到任何好处。此外,请记住,无论何时将大型结构传递或分配给变量,都必须复制它们,这实际上可能是一个性能问题


TL;DR您可能不应该在代码中使用结构,而且它们实际上与不变性没有任何关系。

如果不完全理解您要实现的目标,要给出完整的答案有点困难,但我将从几个重要的区别开始

首先,在C#中,结构/类的区别并不在于可变性本身。你可以有一个不可变的类,就像这个

public class CannotBeMutated
{
    private string someVal;
    public CannotBeMutated(string someVal)
    {
        _someVal = someVal
    }

    public string SomeVal => _someVal;
}
// This is not at all idiomatic C#, please don't use this as an example
public struct MutableStruct
{
      private string _someVal;
      public MutableStruct(string someVal)
      {
          _someVal = someVal;
      }

      public void GetVal()
      {
          return _someVal
      }

      public void Mutate(string newVal)
      {
          _someVal = newVal;
      }
}
还有一个可变结构,比如这个

public class CannotBeMutated
{
    private string someVal;
    public CannotBeMutated(string someVal)
    {
        _someVal = someVal
    }

    public string SomeVal => _someVal;
}
// This is not at all idiomatic C#, please don't use this as an example
public struct MutableStruct
{
      private string _someVal;
      public MutableStruct(string someVal)
      {
          _someVal = someVal;
      }

      public void GetVal()
      {
          return _someVal
      }

      public void Mutate(string newVal)
      {
          _someVal = newVal;
      }
}
使用上面的结构,我可以做到这一点

 var foo = new MutableStruct("Hello");
 foo.mutate("GoodBye");
 var bar = foo.GetVal(); // bar == "GoodBye"!
结构和类之间的区别在于变量传递语义。当值类型的对象(例如结构)被分配给变量、作为参数传递给方法或从方法(包括属性getter或setter)返回时,在将该对象传递给新函数上下文之前,将创建该对象的副本。当引用类型的对象作为参数传递给方法或从方法返回时,不会进行复制,因为我们只传递对象在内存中的位置的引用,而不是对象的副本

关于结构“复制”的另一点。假设您有一个带有引用类型字段的结构,如下所示

public struct StructWithAReferenceType
{
   public List<string> IAmAReferenceType {get; set;}
}
var foo = new Class1();
foo.Struct2 = new Struct2("a", 1);
foo.Struct2 // returns a copy of Struct2("a", 1);
foo.Struct2 = new Struct2("b", 2);
foo.Struct2 // returns a copy of Struct2("b", 2);
Struct2是不可变的,因为调用代码无法一次性更改
StrVar
InVar
的值。指令同样是不变的。然而,数组不是不变的。因此,指令是可变值的不可变容器。类似于如果您有一个
不可变列表
。虽然可以保证调用代码不会将director.StrArray的值更改为其他数组,但对于调用代码更改数组中对象的值,您无能为力

最后,给出一些与您的示例相关的一般建议

首先,可变结构或具有可变字段的结构是不好的。上面的例子应该指出为什么具有可变字段的结构是不好的。Eric Lippert自己在他的博客上有一个很好的例子,说明可变结构是多么可怕

其次,对于大多数使用C语言的开发人员来说,几乎没有理由创建用户定义的va