【问题标题】:Struct and IDisposable结构和 IDisposable
【发布时间】:2017-05-16 14:37:52
【问题描述】:

不知道为什么编译不出来?

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

【问题讨论】:

  • 如果我看到有人在struct 上实现IDisposable,我会开枪的。
  • @leppie:那是随机的人还是实施它的人?很高兴知道。
  • @leppie 你应该解释为什么:-) 我仍然没有看到关于这个原因的解释。为什么与类不同?
  • 作为旁注,即使您使用foreach (var sss in new List<MyStruct>()),也无法做到这一点
  • @leppie 我发现struct : IDisposable 的一个相当合法的用途是滥用using() 以确保使用HtmlTextWriter 编写的XML/HTML 标记元素被正确关闭(struct 的@987654330 @ 方法使用正确的标记名调用 WriteCloseTag - 尽管我的结构是不可变的)。它会生成更易读的代码,类似于呈现的 HTML,因为 using 的缩进就像它呈现的元素一样。由于 CIL 使用 ldloca.s+constrained+callvirt,C# 编译器不会 将结构装箱 - 但我希望它会像 foreach 那样使用鸭子类型。

标签: c# .net idisposable using


【解决方案1】:

很多人都链接到我关于变异值类型以及为什么这是一个坏主意的文章。虽然在理解为什么处置结构是一个坏主意时理解这些概念很重要,而当你这样做时改变结构是一个更糟糕的主意,但这实际上并不是链接到的正确文章。您想阅读的那篇文章详细解释了这一切:

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

简而言之:“使用”会生成值类型的副本,因此您正在处理副本。这意味着您必须非常小心——例如,如果该值是操作系统句柄,则该值的许多副本可能位于内存中,并且您需要确保将其完全释放一次有多少份。

另见If my struct implements IDisposable will it be boxed when used in a using statement?

【讨论】:

  • 我很乐意承认改变“this”而不“告诉任何人”的结构方法是邪恶的(不幸的是,没有标记这些方法的方法,因此在只读上下文中可能禁止使用它们)。然而,我强烈反对结构不应该有公共可变字段的观点。有关简单的代码示例,请参阅stackoverflow.com/questions/441309/why-are-mutable-structs-evil。考虑以下问题:大小为 100 的 Array 中有多少不同的 Point 实例?大小为 100 的 Array 中有多少个不同的 Dictionary 实例?
  • (字典被选为任意类类型——我知道它不是该示例的最佳选择)。如果 someStructType 有一个 public int 字段 I,而 StructArr 是 someStructType 的数组,那么 "StructArr[3].I+=4;" 会有什么效果有 StructArr[4].I 吗?如果 someClassType 有一个 public int 字段 I,而 ClassArr 是 someClassType 的数组,那么 "ClassArr[3].I+=4;" 会产生什么影响在 ClassArr[4].I 上有吗?类当然有自己的位置,但是如果使用类型来表示值,并且如果可变性会有所帮助,那么可变值类型通常比可变类类型更合适。
  • @supercat:我评论了你链接的帖子,但简而言之:你所主张的一切都可以用不可变的值类型来完成。为什么它们应该是可变的
  • @Ron Warholic:对于一个不可变的实体,如果想要更改其中的任何一个,就必须重写所有字段。如果想要更改结构的一个字段,我建议这样做比创建一个几乎所有字段的值都与第一个字段匹配的新结构更清晰、更高效。重写所有内容以进行一次更改可以获得什么可能的优势?
  • @supercat:如果举出改变值类型会产生错误结果的例子是微不足道的,那么改变值类型 clear 究竟如何?人们争论值类型不变性的原因正是因为性能损失(如果有的话)非常值得有效地推理代码的能力。对于绝对最关键的性能领域,可变值类型可能很有用(比如对于 3D 模型中的顶点);但这种情况很少见。
【解决方案2】:

class 和 struct 的场景实际上是一样的,但是你看到的效果不同。

当您将类示例更改为:

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

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

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

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

【讨论】:

  • @Alex:“可变”的东西是可以改变(变异)的东西。可以更改其值的结构称为可变结构,通常被认为是不好的。更多阅读:Why are mutable structs evil?
【解决方案3】:

using 语句的想法是确保在您离开块时处理资源。

当您分配给一个结构变量时,您实际上是用该类型的全新对象替换它。因此,在这种情况下,您将摆脱应该被处置的对象。

【讨论】:

  • 要添加,您将释放非托管资源,同时仍使用该结构的另一个副本。咔嚓!
  • 使用字段/属性时,结构体在 C# 中的写入时复制吗?
  • @xanatos:不,但是 using(和 foreach)构造会创建一个临时变量。类很好,因为这只是一个参考。
  • @xanatos - 没有。您是否正在考虑有时如何在 C++ 中实现字符串?结构只是可变对象,由引用它们的变量单独拥有。变量可能是不可变的(如本例所示),结构的作者可以将结构中的各个字段标记为不可变(通过 readonly 关键字)。
  • @DanielEarwicker 事实上(真正的)问题是/应该是“为什么变量是不可变的”而不是“为什么我不能使用字段”。有趣的是,您甚至不能使用属性(考虑到属性实际上并不需要修改字段)。
【解决方案4】:

考虑以下几点:

 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 已经收到一个引用,因为你在 IFoo f = new Foo() 中装箱。
  • @xanatos:没错。如果您真的想这样做,它会展示如何做;P
  • 那就写吧!对于 75% 的 C# 程序员来说,将值类型转换为接口会导致装箱(在具有约束的泛型情况之外)这一事实并不明显 :-)
  • @xanatos:我真的不想鼓励这样做 :) 如果编码人员知道他/她到底在做什么,这是可以接受的。
【解决方案5】:

我终于理解了 :-) 我会发表我的观点 :-) :-)

现在...

using (MyType something = new MyType())

元等价于

using (readonly MyType something = new MyType())

readonly 与类/结构声明中的 readonly 关键字含义相同。

如果MyType 是一个引用,那么它是“受保护”的引用(而不是被引用的对象)。所以你不能这样做:

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

但你可以

    something.somethingelse = 0;

在 using 块中。

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

所以问题应该是:为什么我不能修改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 正在访问副本。

【讨论】:

  • @leppie 我不得不说实话,只读值类型的字段和属性是只读的这一事实并不是“明显的”,没有任何真正的明显 原因(考虑到值类型仍然可以通过方法改变,并且属性更类似于方法而不是字段)。而且这两个“只读”相同的事实也不明显。
  • readonly 与此无关。这是结构与类的语义。前者是价值,后者是引用。如果你“引用”前者,则复制该值,后者复制引用(但仍指向原始对象)。
  • 另外,变量名称相同(但位置不同,新的变量覆盖了前一个变量)这一事实也令人困惑。
  • @leppie 这是纯烟雾。如果某些东西是“只读的”,那么写入它应该会使一切爆炸。 JIT 至少应该用抛出异常的方法替换对结构的只读实例的方法的调用(并且 JIT 确切地知道结构何时在安全代码中是只读的,因为它不能被引用/输出,如果它被取消装箱,那么它就是一个副本,如果它是函数的参数,那么它就是一个副本)。事实上,这只是“有可能做到,但我们不会让你”。这方面的证据是财产是被禁止的。这是一条句法规则,而不是“必然”规则。
  • 嗨,我来自未来,说它并不是真正的元等效于只读字段。 Readonly struct 字段在调用方法时会创建防御性副本,而在 using 语句中创建的变量不会,因此它们仍然可以通过调用其方法来改变。此外,它会是突变的结构,即被处置的结构。自己看:dotnetfiddle.net/EPjjCN
【解决方案6】:

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

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

【讨论】:

  • 这个答案是不正确的,因为它是 C# - 结构不能被重新分配,但它可以被改变(它的内容可以被修改)。但无论如何,可变结构是一个糟糕的主意,所以可以说这个答案比语言本身更正确。 :)
  • 我同意@DanielEarwicker 的观点。
【解决方案7】:

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

编译器在这种情况下允许你修改类的字段而不是结构的原因与内存管理有关。对于类(即引用类型),不是对象,而是引用本身就是值。因此,当您修改该对象中的字段时,您不是在操作该值,而是在其他地方的一个内存块,由该值引用。在结构体的情况下,对象就是值,所以当你在结构体中操作一个字段时,实际上是在操作此时被认为是只读的值。 p>

编译器允许方法调用(进而修改字段)的原因仅仅是它无法深入分析以确定方法是否执行此类修改。

在 MS Connect 上有一个(已结案)案例,这可能会带来更多启示:Cannot assign to field of struct with IDisposable and when it is a using variable - CS1654 error

【讨论】:

  • 不太正确。查看为using(和foreach)生成的代码,它们都创建了一个分配给的临时变量。在结构的情况下,值被复制。
  • @leppie:是的,但请注意,OP 不是分配给 using 变量,而是对象中的一个字段(它是一个可变结构)。还是我误会了你?
  • OP 正在分配给结构副本上的字段。虽然从技术上讲代码是有效的,但编译器会拒绝它,因为它没有任何效果(并指示逻辑错误)。
  • 这解释了规则,但不是规则背后的原因。他们试图通过引入这条规则来解决什么问题?
  • 添加到上面的评论,通过一些技巧你可以绕过这个限制。提示:装箱的值类型。
【解决方案8】:

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

但是,允许调用Set(),因为编译器无法知道调用会改变结构的状态。实际上,这是一种改变只读值的巧妙方法!

查看 Eric Lippert 的 mutating readonly structs 帖子了解更多信息。

【讨论】:

  • 这与readonly无关。字段永远不会隐含readonly
  • 另外,使用Set() 改变readonly 字段也不起作用。编译器错误。
  • @leppie:在 Set() 中,该字段不是只读的。但是,在示例中,结构实例是只读的,因为它位于 using 块中。该编译器由此推断结构上的任何公共字段也将是只读的。
  • 你错了。编译器不会推断任何此类细节。除了在构造函数中之外,您不能改变 readonly 字段(注意,现在不是根据结构谈论值语义,我怀疑您指的是)。
  • @leppie:结构类型的存储位置无非就是将该类型的字段连接在一起。无法写入位于只读存储位置的结构的字段,也无法将只读结构位置作为ref 参数传递。由于结构类型上的实例成员将this 作为ref 参数,因此实际上不可能调用只读结构上的实例成员。不幸的是,C# 假设​​如果readOnlyStruct.method() 不能将readOnlyStruct 作为method 参数传递给ref,...
猜你喜欢
  • 2011-04-22
  • 1970-01-01
  • 2013-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-02
相关资源
最近更新 更多