【问题标题】:How to make struct immutable inside a class definition如何在类定义中使结构不可变
【发布时间】:2019-01-03 21:32:20
【问题描述】:

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

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

这是否也保持不变性?

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

我担心的是因为我在类定义中做了一个 {set;} 我在某处违反了规则。我会将结构体放在类定义本身中,但我不喜欢一遍又一遍地不断调用类构造函数来创建每个结构体,这似乎违背了首先使用结构体的目的。

【问题讨论】:

  • public Struct {get; set;} 不是有效代码。您必须指定属性的类型和名称。
  • 请注意,Array 不是不可变的,因此,如果您无法分配新的 Array,您可以更改它的元素值。试试看:InStruct ist = new InStruct(new int[]{0,1,2}); ist.StrArray.SetValue(10,0);

标签: c# class struct immutability


【解决方案1】:

如果不确切了解您要完成的工作,很难给出完整的答案,但我将从几个重要的区别开始。

首先,在 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"!

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

关于 struct 'copying' 的补充说明。想象一下,你有一个结构体,它的字段是引用类型,像这样

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

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

public void MessWithYourSruct(StructWithAReferenceType t) { t.IAmAReferenceType.Add("哈哈"); }

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

// 甚至更令人不安 var s = new StructWithAReferenceType { IAmAReferenceType = new List()}; 变量 t = s; // 复制 s s.IAmAReferenceType.Add("hi"); t.IAmAReferenceType.Count; // 1!

即使复制一个结构,它的引用类型字段仍然引用内存中的相同对象。

immutable/mutable 和 struct/class 的区别有些相似,因为它们都是关于在哪里以及是否可以更改程序中对象的内容,但它们仍然非常不同。

现在回答你的问题。在您的第二个示例中,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 同样是不可变的。但是,Array 不是不可变的。所以 InStruct 是一个可变值的不可变容器。类似于如果您有ImmutableList&lt;List&lt;string&gt;&gt;。虽然您可以保证调用代码不会将 InStruct.StrArray 的值更改为不同的数组,但您不能对调用代码更改数组中的对象的值进行任何操作。in

最后,一些与您的示例相关的通用建议。

首先,可变结构或具有可变字段的结构是不好的。上面的例子应该指出为什么具有可变字段的结构是不好的。 Eric Lippert 本人在他的博客 here

上有一个很好的例子来说明可变结构是多么可怕

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-26
    • 2015-01-21
    • 1970-01-01
    相关资源
    最近更新 更多