17

I wonder why does it not compile?

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

UPDATE: But it works perfect. Why?

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");
    }
 }
Alexandre
  • 13,030
  • 35
  • 114
  • 173
  • 15
    I will shoot people if I ever saw anyone implementing `IDisposable` on a `struct`. – leppie Oct 27 '11 at 10:14
  • 12
    @leppie: will that be random people or the person implementing it? Could be good to know. – Fredrik Mörk Oct 27 '11 at 10:19
  • 3
    @leppie You should explain why :-) And I still don't see the explanation on the reason of this. Why with class(es) it's different? – xanatos Oct 27 '11 at 10:24
  • As a sidenote, you can't do it even when you are using a `foreach (var sss in new List())` – xanatos Oct 27 '11 at 10:30
  • @xanatos: See Daniel Earwicker's answer. – leppie Oct 27 '11 at 10:31
  • @leppie Daniel's response isn't ver clear. – xanatos Oct 27 '11 at 10:38
  • Eric Lippert has an interesting post on mutating readonly structs: http://blogs.msdn.com/b/ericlippert/archive/2008/05/14/mutating-readonly-structs.aspx – Sean Oct 27 '11 at 10:54
  • Struct members other than property setters which mutate 'this' are evil in any environment which cannot distinguish them from struct members which do not (including .net) but that doesn't mean public struct fields are evil. Struct fields have particular semantics which are different from class semantics, but are much more self-contained and predictable. Given an array of Rectangle of size 100 will hold 100 different instances of Rectangle. Guaranteed. If Rectangle were a class, an array of length 100 could hold 100 different instances, 100 references to one instance, or anything between. – supercat Nov 13 '11 at 23:07
  • 4
    @leppie I found a quite legitimate use for `struct : IDisposable` is abusing `using()` to ensure XML/HTML markup elements being written with `HtmlTextWriter` are correctly closed (the `struct`'s `Dispose` method calls `WriteCloseTag` with the correct tag-name - though my struct is immutable). It results in much more readable code that resembles the rendered HTML because the `using` is indented just like the element it's rendering. The C# compiler will **not** box the struct because the CIL uses `ldloca.s+constrained+callvirt` - but I was hoping it would use duck-typing like `foreach` does. – Dai Jun 25 '20 at 06:43

8 Answers8

32

A number of people have linked to my article about mutating value types and why it is a bad idea. Though it is important to understand those concepts when understanding why its a bad idea to dispose a struct, and a worse idea to mutate the struct when you do, that is not actually the right article to link to. The one you want to read that explains all this in excruciating detail is:

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

In short: "using" makes a copy of the value type, and you are therefore disposing a copy. That means you have to be very careful -- if the value is, say, an OS handle, there might be lots of copies of that value lying around memory, and you'll need to make sure that you dispose it exactly once no matter how many copies there are.

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

Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I'll readily grant that struct methods which alter "this" without "telling anyone" are evil (it's unfortunate that there's no means of tagging such methods so their use could be forbidden in read-only contexts). I disagree strenuously, however, with the notion that structs should not have public mutable fields; see http://stackoverflow.com/questions/441309/why-are-mutable-structs-evil for simple code examples. Consider these questions: How many distinct Point instances are in an Array of size 100? How many distinct Dictionary instances are in an Array of size 100? – supercat Oct 27 '11 at 19:43
  • 1
    (Dictionary was chosen as an arbitrary class type--I know it's not the best for that example). If someStructType has a public int field I, and StructArr is an array of someStructType, what effect will "StructArr[3].I+=4;" have on StructArr[4].I ? If someClassType has a public int field I, and ClassArr is an array of someClassType, what effect will "ClassArr[3].I+=4;" have on ClassArr[4].I? Classes certainly have their place, but if a type is used to represent values, and if mutability would be helpful, then a mutable value type is often more appropriate than a mutable class type. – supercat Oct 27 '11 at 19:55
  • @supercat: I commented on the post you linked but in short: everything you argue for can be done with immutable value types. Why should they specifically be *mutable*? – Ron Warholic Oct 27 '11 at 23:23
  • @Ron Warholic: With an immutable entity, one must rewrite all the fields if one wants to change any of them. If what one wants to do is change one field of a struct, I would suggest that doing so is both clearer and more performant than creating a new structure in which nearly all of the fields have values that happen to match those of the first. What possible advantage is gained by rewriting everything in order to make one change? – supercat Oct 28 '11 at 15:17
  • @supercat: How exactly is mutating a value type **clear** when it is trivial to give examples where mutating a value type produces incorrect results? The reason people argue for immutability on value types is precisely because the performance loss (if any) is well worth the ability to reason about the code effectively. For the absolute most performance critical areas mutable value types can be useful (say for vertices in a 3D model); but those types of situations are few and far between. – Ron Warholic Oct 28 '11 at 16:12
  • @Ron Warholic: Outside of value-type methods mutating "this" (which I'll readily acknowledge is fraught with peril), what examples can you offer where code which writes to struct *fields* compiles but offers results other than what would be expected by someone who knows what struct fields are, and that the entity in question is a struct? The normal "examples" I've seen, I would consider to self-evidently broken (i.e. knowing that the thing being written is a struct field would be sufficient to know that the code, as written, couldn't *possibly* work). – supercat Oct 28 '11 at 16:19
  • @supercat: Doing so is essentially saying "I want to take what is physically one storage location and treat it logically as collection of independently mutatable storage locations." And that's fine; obviously people do that all the time; fields in classes, elements in arrays, and so on, are all about dividing up a block of storage into independent parts. The reason why mutable value types are so bad is because *values of reference type are references that identify that root storage location*. But *values of value type are not storage locations*. Better to treat values as *immutable values*. – Eric Lippert Oct 28 '11 at 16:21
  • @Ron Warholic: Although generic type parameters which could be either class or value types can be tricky (it's easy to write code which will work for class types, but not for value types), the same problems that exist for mutable value types *also exist for immutable value types*, so the proper solution is for generic code which hasn't been deliberately designed to be value-type compatible to apply a 'class' constraint. – supercat Oct 28 '11 at 16:26
  • @Eric Lippert: If I have a MyStruct[] of length 100, how many distinct instances of MyStruct does it contain? It holds 100, guaranteed. If I have a MyClass[] of length 100, how many distinct instances of MyClass does it hold? Will it always hold that many? How can you know? If one declares a variable of a mutable value type (e.g. Rectangle), each field of that variable is a storage location to which one is (in the absence of unsafe code) exclusive access, no matter how the struct is defined. One can call a function with a value-type ref parameter and still have exclusive access to... – supercat Oct 28 '11 at 16:37
  • @Eric Lippert: ...the fields of that struct once the function returns. By contrast, if one passes a class-type object to a function, one can never again be assured of exclusive access to anything within that object. For that matter, unless one inspects the code for the class in question, even one can't be sure that even constructing a new instance will guarantee exclusivity. In an earlier post of yours, you complain about the semantics of passing or returning arrays when what one really wants to pass or return is a list of values. Mutable classes have the same problem. – supercat Oct 28 '11 at 16:43
  • @supercat: Agreed; so, what's the best practice to mitigate that potential problem? The entity being modelled is either logically immutable or mutable, and has either referential or value identity. If immutable-referential, use an immutable class. Mutable-ref? Use a mutable class. Immutable-value? Use an immutable value type if small (eg, Point), or an immutable reference type with value equality if large (eg, String). Mutable-value? Avoid unless you have a compelling reason not to; it is too easily confused with mutable-ref. – Eric Lippert Oct 28 '11 at 16:56
  • @Eric Lippert: The "problem" I mentioned was with mutable classes, and my solution, which you don't like, is to use a struct with public fields. A routine which is passed such a struct by value receives a set of values (those of its fields). A routine which is passed such a struct by reference is *temporarily* given a set of variables, but it has to give them up when it exits. Incidentally, I would consider "Point" a nice example of a *mutable* value type; why would you not consider "MyPoint.X = 5;" much cleaner and clearer than "MyPoint = new Point(5, MyPoint.Y);" – supercat Oct 28 '11 at 21:30
  • @Eric Lippert: I can appreciate that from a compiler-writer's perspective, certain aspects of how structs are handled in .net make it difficult to work with structures (particularly the facts that all structs are assumed to be boxable, and that--unless constrained otherwise--generic type parameters are expected to work with structs and classes interchangeably). The job of language constructs, however, isn't generally supposed to be to make life easy for the compiler writer--it's supposed to make life easy for people who have to write and maintain production code. – supercat Oct 28 '11 at 21:43
3

The idea of a using statement is to ensure that a resource is disposed of when you leave the block.

When you assign to a struct variable, you are effectively replacing it with a completely new object of that type. So in this context you would have got rid of the object that was supposedly going to be disposed of.

Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
  • To add, you will be disposing the unmanaged resources while still using another copy of the struct. KABOOOM! – leppie Oct 27 '11 at 10:33
  • Are struct copy-on-write in C# when using fields/properties? – xanatos Oct 27 '11 at 10:37
  • @xanatos: No, but the using (and foreach) constructs create a temporary variable. Classes are good since this is only a reference. – leppie Oct 27 '11 at 10:40
  • @xanatos - no. Are you thinking about how strings are sometimes implements in C++? Structs are just mutable objects that are solely owned by the variable that refers to them. The variable may be immutable (as in this case), and the author of the struct may mark individual fields within the struct as immutable (via the `readonly` keyword). – Daniel Earwicker Oct 27 '11 at 10:42
  • @DanielEarwicker And in fact the (real) question was/should be "why the variable is immutable" not "why I can't use fields". And it's interesting that you can't use even properties (considering that a property doesn't really NEED to modify a field). – xanatos Oct 27 '11 at 10:45
3

The class and struct scenarios are actually the same but you see different effects.

When you change the class example to :

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

You will get the same error on the first assignment.

The explanation is: You cannot change (mutate) the using-variable. But for a class that applies to the reference, not to the instance.

And the lesson is: Don't use structs. And especially don't use mutable structs.

H H
  • 263,252
  • 30
  • 330
  • 514
  • 2
    @Alex: something that is "mutable" is something that can change (mutate). A struct that can change its value is called a mutable struct, and is generally considered something bad. More reading: [Why are mutable structs evil?](http://stackoverflow.com/questions/441309/why-are-mutable-structs-evil) – Fredrik Mörk Oct 27 '11 at 12:00
2

Consider the following:

 interface IFoo: IDisposable { int Bar {get;set;}}

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

Now do:

  IFoo f = new Foo();

  using (f)
  {
    f.Bar = 42;
  }

  Console.WriteLine(f.Bar); 

This prints:

Disposed: 42
42
leppie
  • 115,091
  • 17
  • 196
  • 297
  • In this example, using is already receiving a reference, because you are boxing in the IFoo f = new Foo(). – xanatos Oct 27 '11 at 10:55
  • @xanatos: Exactly. It is showing how to do it, if you really wanted to ;P – leppie Oct 27 '11 at 11:00
  • 2
    Then write it! The fact that casting to an interface a value type causes boxing (outside of the generic-with-constraint case) isn't evident for 75% of the C# programmers :-) – xanatos Oct 27 '11 at 11:05
  • @xanatos: I really dont want to encourage this :) It is acceptable though if the coder knows what he/she is doing exactly. – leppie Oct 27 '11 at 11:09
2

Finally I have comprehended it :-) I'll post my view :-) :-)

Now...

using (MyType something = new MyType())

is meta-equivalent to

using (readonly MyType something = new MyType())

with readonly having the same meaning of the readonly keyword in class/struct declaration.

If MyType is a reference, then it is the reference (and not the referenced object) that is "protected". So you can't do:

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

but you can

    something.somethingelse = 0;

in the using block.

If MyType is a value type, the readonly "modifier" extends to its fields/properties. So they didn't introduced a new type of "const-ness/readonly-ness" in using, they simply used the one they had.

So the question should be: why can't I modify fields/properties of readonly value types?

Note that if you do this:

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

Result

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

so the using is doing a "private" copy of sss, and sss.n = 12 is accessing the "original" sss, while Dispose is accessing the copy.

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • @leppie I have to tell the truth, the fact that fields and properties of read-only value types are read-only isn't something "evident" There isn't any real **evident** reason for this (considering that the value type is still mutable by methods, and that properties are more similar to methods than to fields). And the fact that the two "read-onlyness" are the same isn't evident either. – xanatos Oct 27 '11 at 11:07
  • readonly has nothing to do with this. It is the semantics of structs vs classes. the former is by value, the latter is by reference. If you 'reference' the former, a copy of the value is made, the latter a copy of the reference is made (but still pointing to the original object). – leppie Oct 27 '11 at 11:13
  • Also the fact that the variable name is the same (but different location, iow a fresh variable shadowing the previous) is confusing. – leppie Oct 27 '11 at 11:14
  • @leppie This is pure smoke. If something is "readonly" then writing to it should make everything explode. The JIT should at least substitute calls to methods of readonly instances of structs with methods that throw an Exception (and the JIT knows exactly when a struct is readonly in safe code, because it can't be ref/out, if it's unboxed then it's a copy, if it's a parameter of a function then it's a copy). As is, it's only a "it's possible to do it, but we won't let you". The evidence of this is that properties are forbidden. It's a synctactical rule and not a "by necessity" rule. – xanatos Oct 27 '11 at 11:18
  • @xanatos: The problem is that .net doesn't provide an enforceable "in-ref" parameter type, and struct methods take the "this" parameter as a "ref", rather than "in-ref" type. Thus, the system couldn't really enforce the "read-only-ness" of value types properly without precluding the use of *any* methods or properties on value types in many contexts. Beyond that, the essential definition of an immutable value type is that its *fields* can never be changed. Property getters are allowed and setters are not, based on the assumption that getters won't write to structure fields but setters will. – supercat Oct 27 '11 at 19:15
  • @xanatos I still don't understand how you got `During dispose: 0`. I also made a test here (http://i.stack.imgur.com/rCH6V.jpg) and I dont see anything wrong. – Royi Namir Feb 12 '14 at 13:01
  • @RoyiNamir You haven't comprehended the example :-) See here the full example: http://ideone.com/F22MHw (I've changed the values to make it clearer). The `0` was the default value of `n`. The point of the example is that the `Dispose` isn't done on the `sss` you declared but on a copy of it, because `MyStruct` is a value type. So if you change its value inside the `using`, the `Dispose` won't see the "new" value because it is using a copy of the `struct` from "before" the value was changed. – xanatos Feb 15 '14 at 08:42
  • 1
    Hi, I came from the future to say it's not really meta-equivalent to readonly fields. Readonly struct fields create defensive copies when a method is called, variables created in using statements don't, so they can still be mutated by calls to their methods. Also, it'd be the mutated struct the one that is disposed. See for yourselves: https://dotnetfiddle.net/EPjjCN – Oscar Abraham Aug 07 '21 at 07:14
1

Within the using block, the object is read-only and cannot be modified or reassigned.

check this link: http://msdn.microsoft.com/en-us/library/yh598w02.aspx

Emir Akaydın
  • 5,708
  • 1
  • 29
  • 57
  • This answer is incorrect re: C# as it stands - the struct cannot be reassigned, but it can be mutated (its contents can be modified). But mutable structs are a terrible idea anyway, so arguably this answer is more correct than the language itself. :) – Daniel Earwicker Oct 27 '11 at 10:36
  • I agree with @DanielEarwicker here. – leppie Oct 27 '11 at 10:40
1

I am not 100% sure on this, so anyone please correct me if I'm wrong.

The reason that the compiler allows you to modify fields of a class in this scenario, but not a struct, is related to memory management. In the case of a class (i.e a reference type), the not the object, but the reference itself is the value. So when you modify a field in that object, you are not manipulating the value, but a memory block somewhere else, referenced by the value. In the the case of a struct, the object is the value, so when you manipulate a field in the struct, you are in fact manipulating the value that is considered read-only at this time.

The reason the compiler will allow the method call (that in turn modifies a field) is simply that it cannot analyze deeply enough to determine whether the method performs such modification or not.

There is a (closed) case at MS Connect regarding this, that may shed some more light: Cannot assign to field of struct with IDisposable and when it is a using variable - CS1654 error

Fredrik Mörk
  • 155,851
  • 29
  • 291
  • 343
  • Not quite correct. Look at the code generated for `using` (and `foreach`), both create a temporary variable that is assigned to. In the case of the struct, the value is copied. – leppie Oct 27 '11 at 10:34
  • @leppie: yes, but note that the OP is not assigning to the using variable, but a field in the object (it's a mutable struct). Or did I misunderstand you? – Fredrik Mörk Oct 27 '11 at 10:37
  • The OP is assigning to a field on a copy of the struct. WHile technically the code is valid, the compiler rejects it, as it will have no effect (and indicates a logical error). – leppie Oct 27 '11 at 10:39
  • This explains the rule, but not the reason behind the rule. What was the problem they tried to resolve by introducing this rule? – xanatos Oct 27 '11 at 10:40
  • To add to above comment, with some tricks you can get around this constraints. Hint: boxed value types. – leppie Oct 27 '11 at 10:41
  • @leppie this is s##t, because he is assigning the value to the only sss that is visible in his scope. If after the Set he does `Console.WriteLine(sss.n)` he will see 12. If he inserts `Console.WriteLine(sss.n)` in the `Dispose` method, even that will show `12`. So the `Dispose` is acting on the same "copy" of the struct where the `Set` is acting and that copy seems to be the only one visible. – xanatos Oct 27 '11 at 10:43
  • I'm afraid I don't follow you @leppie. As I can see, the using statement creates a variable `sss` and assigns a `MyStruct` instance to it. Within the using block, an assignment is made to a field within that same instance. I don't see the other copy you talk about (even in IL code). The rationale behind making the using variable read-only (as I understand it) is not to prevent the object from mutating (after all, there is no such limitation imposed on reference types), but to prevent *the variable itself* to change. In the case of a struct, mutating the object will mutate the value. – Fredrik Mörk Oct 27 '11 at 10:51
  • `stloc.0` `ldloc.0` `stloc.2` `ldloc.0` (in using) then `ldloc.2` in finally (another copy) – leppie Oct 27 '11 at 11:03
  • @leppie: I don't see that here. If you take the OP code sample (minus the assignment, to make it compile), the emitted IL code will a) push the address of the using variable onto the evaluation stack (`ldloca.s`), b) use `ldloc.0` to get to the value in the using block (which means it will load the variable from the address that was just pushed, right?), and c) push the address of the using variable onto the evaluation stack in the finally block. It may very well be that I miss something (opcodes is not quite my field), but as far as I can see, there is only one copy involved? – Fredrik Mörk Oct 27 '11 at 11:47
  • Code and IL available here, if anyone wants to examine: http://pastebin.com/GvbTNjyD – Fredrik Mörk Oct 27 '11 at 11:47
  • `ldloc.xxx` load the value or reference depending on the type. AFAIK, a copy is made every time that instruct is encountered. – leppie Oct 27 '11 at 11:53
0

The struct is flagged as read-only. You're trying to modify a public member variable, which the compiler flags as read-only and so prohibits.

However, the call to Set() is allowed because the compiler has no way of knowing the the call will mutate the state of the struct. It is, in effect, a crafty way of mutating a readonly value!

Take a look at Eric Lippert's post of mutating readonly structs for more info.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
Sean
  • 60,939
  • 11
  • 97
  • 136
  • This has nothing to do with `readonly`. Fields are never implicitly `readonly`. – leppie Oct 27 '11 at 11:07
  • Also, using `Set()` to mutate a `readonly` field will not work. Compiler error. – leppie Oct 27 '11 at 11:07
  • @leppie: Within Set() the field isn't readonly. However, in the example the struct instance is readonly as it's within a `using` block. This compiler infers from this that any public field on the struct will also be readonly. – Sean Oct 27 '11 at 11:15
  • You are mistaken. The compiler does not infer any such details. You cannot mutate a `readonly` field (note, not talking about value semantics as per structs now, which I suspect you are referring to) except in the constructor. – leppie Oct 27 '11 at 11:18
  • @leppie: A storage location of a structure type is nothing more nor less than then fields of that type concatenated together. It is not possible to write to the fields of a struct residing in a read-only storage location, nor is it possible to pass a read-only struct location as a `ref` parameter. Since instance members on structure types take `this` as a `ref` parameter, it's not really possible to invoke instance members on read-only structures. Unfortunately, C# assumes that if `readOnlyStruct.method()` can't pass `readOnlyStruct` to `method` as a `ref` parameter, ... – supercat Oct 09 '13 at 21:39
  • ...the compiler should automatically rewrite the code as `var temp = readOnlyStruct; temp.method();` without giving the structure type any say in the matter. This will ensure that none of the fields of the `readonly` structure can get written, but the extra copying will slow down code in cases where it isn't necessary, and will at best swap one set of incorrect semantics for another if code does try to write it. – supercat Oct 09 '13 at 21:45