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

时间:2018-07-26 19:41:40

标签: c# class struct immutability

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

namespace space
{
    class Class1
    {
        public Struct {get; set;}
    }
    public Struct
    {
        public Struct(string strVar)
        {
            StructVar = strVar;
        }
        public string StructVar {get;}
    }
}

此外,如果我在类似struct的结构中有一个结构,

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

这还能保持不变性吗?

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

我担心的是,因为我在类定义中执行了{set;},所以我在某处违反了一条规则。我将结构放在类定义本身中,但是我不希望不得不一遍又一遍地不断调用类构造函数来创建每个结构,这种类型的想法似乎首先破坏了使用结构的目的。 >

1 个答案:

答案 0 :(得分:2)

在不完全了解您要完成的工作的情况下给出完整的答案有些困难,但是我将从一些重要的区别开始。

首先,在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;
      }
}

使用上述结构,我可以做到

 var foo = new MutableStruct("Hello");
 foo.mutate("GoodBye");
 var bar = foo.GetVal(); // bar == "GoodBye"!

结构和类之间的区别在于变量传递语义。当将值类型的对象(例如struct)分配给变量,作为参数传递给方法或从方法(包括属性getter或setter)返回时,在将对象复制到新对象之前对其进行复制。功能上下文。当引用类型的对象作为参数传递给方法或从方法返回时,不会进行任何复制,因为我们仅将引用传递给对象在内存中的位置,而不是对象的副本。宾语。

关于“复制”结构的另一点。假设您有一个结构,其字段是引用类型,就像这样

public struct StructWithAReferenceType
{
   public List<string> IAmAReferenceType {get; set;}
}

当将此结构的实例传递给方法时,将复制对List的引用的副本,但不会复制基础数据。所以如果你这样做

公共无效MessWithYourSruct(StructWithAReferenceType t)    {         t.IAmAReferenceType.Add(“ HAHA”);    }

var s = new StructWithAReferenceType {IAmAReferenceType = new List()};    MessWithYourSruct(s);    s.IAmAReferenceType.Count; // 1!

//甚至更令人不安    var s = new StructWithAReferenceType {IAmAReferenceType = new List()};    var t = s; //制作s的COPY    s.IAmAReferenceType.Add(“ hi”);    t.IAmAReferenceType.Count; // 1!

即使复制了结构,其引用类型字段仍引用内存中的相同对象。

不可变/可变和结构/类的区别有些相似,只是它们在何处以及是否可以更改程序中对象的内容,但是它们仍然非常不同。

现在继续您的问题。在第二个示例中,Class1不是不可变的,因为您可以像这样更改Struct2的值

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是不可变的,因为无法调用代码一次更改StrVarInVar的值。 InStruct同样是不可变的。但是,数组不是 不可变的。因此,InStruct是一个可变值的不可变容器。类似于您有ImmutableList<List<string>>。虽然可以保证调用代码不会将InStruct.StrArray的值更改为其他数组,但是对于调用代码更改Array中的对象 的操作则无能为力。

最后,一些与您的示例有关的一般建议。

首先,可变结构或具有可变字段的结构是不好的。上面的示例应指出为什么具有可变字段的结构是不好的。埃里克·利珀特(Eric Lippert)本人在他的博客here

上有一个很好的例子说明了可怕的可变结构

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

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