结构和IDisposable

我想知道为什么不编译?

public static void Main(string[] args) { using (MyStruct sss = new MyStruct()) { sss.s = "fsdfd";// Cannot modify members of 'sss' because it is a 'using variable' //sss.Set(12); //but it's ok } } public struct MyStruct : IDisposable { public int n; public string s; public void Set(int n) { this.n = n; } public void Dispose() { Console.WriteLine("dispose"); } } 

更新 :但它完美无缺。 为什么?

 public static void Main(string[] args) { using (MyClass sss = new MyClass()) { sss.Field = "fsdfd"; } } public class MyClass:IDisposable { public string Property1 { get; set; } public string Field; public void Method1 (){} public void Dispose() { Console.WriteLine("dispose class"); } } 

类和结构场景实际上是相同的,但您会看到不同的效果。

将类示例更改为:

 using (MyClass sss = new MyClass()) { sss = null; // the same error sss.Field = "fsdfd"; // ok } 

您将在第一次分配时收到相同的错误。

解释是:你不能改变(变异)使用变量。 但对于适用于引用的类,而不是实例。

教训是:不要使用结构。 特别是不要使用可变结构。

很多人都把我的文章与变异值类型以及为什么这是一个坏主意联系起来。 虽然理解这些概念很重要,但是在理解为什么它是一个糟糕的想法来处理一个结构,并且更糟糕的想法在你做的时改变结构,这实际上并不是正确的链接文章。 您想要阅读的那个以令人难以忍受的细节解释所有这些:

http://ericlippert.com/2011/03/14/to-box-or-not-to-box/

简而言之:“使用”会生成值类型的副本 ,因此您正在处理副本 。 这意味着你必须非常小心 – 如果值是一个操作系统句柄,那么该值可能有很多副本存在于内存中,你需要确保你准确地处理它一次无论如何有多少份。

另请参见如果我的struct实现了IDisposable,那么在using语句中使用时它是否会被装箱?

using声明的想法是确保在离开块时处理资源。

当您分配给struct变量时,您实际上将其替换为该类型的全新对象。 因此,在这种情况下,你将摆脱据称将要被处置的对象。

考虑以下:

  interface IFoo: IDisposable { int Bar {get;set;}} struct Foo : IFoo { public int Bar { get; set; } public void Dispose() { Console.WriteLine("Disposed: {0}", Bar); } } 

现在做:

  IFoo f = new Foo(); using (f) { f.Bar = 42; } Console.WriteLine(f.Bar); 

这打印:

 Disposed: 42 42 

最后我理解了它:-)我会发表我的看法:-) 🙂

现在…

 using (MyType something = new MyType()) 

是元等价的

 using (readonly MyType something = new MyType()) 

readonly与class / struct声明中的readonly关键字具有相同的含义。

如果MyType是引用,则它是“受保护”的引用(而不是引用的对象)。 所以你做不到:

 using (readonly MyType something = new MyType()) something = null; 

但是你可以

  something.somethingelse = 0; 

在使用块中。

如果MyType是值类型,则readonly “修饰符”将扩展到其字段/属性。 所以他们没有在using引入一种新的“常量/只读”,他们只是使用了他们的那种。

所以问题应该是:为什么我不能修改readonly值类型的字段/属性?

请注意,如果您这样做:

 public void Dispose() { Console.WriteLine("During dispose: {0}", n); } var sss = new MyStruct(); using (sss) { sss.n = 12; Console.WriteLine("In using: {0}", sss.n); // 12 } Console.WriteLine("Outside using: {0}", sss.n); // 12 

结果

 In using: 12 During dispose: 0 Outside using: 12 

因此, using正在执行sss的“私有”副本,并且sss.n = 12正在访问“原始” sss ,而Dispose正在访问副本。

在using块中,该对象是只读的,不能修改或重新分配。

检查此链接: http : //msdn.microsoft.com/en-us/library/yh598w02.aspx

我对此并不是100%肯定,所以如果我错了,请任何人纠正我。

编译器允许您在此方案中修改类的字段而不是结构的原因与内存管理有关。 在类(即引用类型)的情况下, 不是对象,而是引用本身就是值 。 因此,当您修改该对象中的字段时,您不是在操纵值,而是在其他位置操作值,由值引用。 在struct的情况下, 对象是值 ,因此当您操作struct中的字段时,实际上是在操作此时被认为是只读的值。

编译器允许方法调用(反过来修改字段)的原因很简单,它无法深入分析以确定方法是否执行此类修改。

关于这一点,在MS Connect上有一个(封闭的)案例,可能会有更多的亮点: 无法分配给具有IDisposable的struct字段以及何时使用变量 – CS1654错误

该结构被标记为只读。 您正在尝试修改公共成员变量,编译器将其标记为只读,因此禁止。

但是,允许调用Set() ,因为编译器无法知道调用将改变结构的状态。 实际上,它是一种改变读取值的狡猾方式!

看一下Eric Lippert的变更readonly结构的post了解更多信息。