6

Whenever I tried to search about differences between classes and structs in C# or .net, I ended up with the conceptual overview of the two things like value type or the reference type, where the variables are allocated etc. But I need some practical differences. I have found some like different behavior of assignment operator, having constructors etc. Can anybody provide some more practical differences which will be directly useful while coding? Like the things works with one but not with other or same operation showing different behavior. And some common mistakes regarding these two.

Also please suggest where to consider using a struct instead of a class. And where the structs should not be used.

Edit: Do I have to call the constructor explicitly or just declaring a struct type variable will suffice?(Should I make it a new question?)

Gulshan
  • 3,611
  • 5
  • 35
  • 46

8 Answers8

10

OK, here are a few specific, practical differences:

  • A variable can be null if it’s a class, but is never null if it’s a struct.

  • default(T) is null for a class, but for a struct actually constructs a value (consisting of lots of binary zeros).

  • A struct can be made nullable by using Nullable<T> or T?. A class cannot be used for the T in Nullable<T> or T?.

  • A struct always has a public default constructor (a constructor with zero parameters). The programmer cannot override this constructor with a custom implementation — it is basically “set in stone”. A class allows the programmer to have no default constructor (or a private one).

  • The fields in a class can have default values declared on them. In a struct they can’t.

  • A class can inherit from another class, but a struct cannot be declared to derive from anything (it implicitly derives from System.ValueType).

  • It makes sense to use a class in object.ReferenceEquals(), but using a struct variable will always yield false.

  • It makes sense to use a class in a lock() statement, but using a struct variable will cause very subtle failure. The code will not be locked.

  • On a 32-bit system, you can theoretically allocate an array of up to 536,870,912 references to a class, but for a struct you need to take the size of the struct into account because you are allocating actual instances.

Ondrej Tucny
  • 27,626
  • 6
  • 70
  • 90
Timwi
  • 65,159
  • 33
  • 165
  • 230
  • 1
    Structs don't really have a public default constructor. Rather, structs that are part of some other entity (e.g. elements of an array or fields within a class, or fields of another struct meeting the above criteria) *implicitly* come into existence when the entity holding them comes into existence, without *any* constructor being called, whereas class instances only come into existence when explicitly instantiated, which, absent Reflection, can only happen by either invoking a constructor or by a class method which invokes `MemberwiseClone`. – supercat Jan 23 '12 at 19:57
  • Structs have a public default constructor in every sense of the word: You can invoke it, you can discover it via Reflection, and it satifies the `new()` generic type constraint. Just because there are ways to construct a struct without explicitly calling such a constructor doesn’t suddenly make it disappear :) – Timwi Jan 24 '12 at 17:21
  • Reflection with structs is weird. Actual entities of "real" struct types are outside the .net type system, but every struct type has a corresponding class type which derives from `ValueType`; .net provies a widening cast operator from each struct type to `Object/ValueType` (which converts it to a class-type object) and a narrowing cast from the class-type object to the "real" struct. When one uses Reflection to create a new instance of a struct, what will be createde is a ValueType derivative; if one casts and assigns it to a struct, it will be converted into that struct type. – supercat Jan 24 '12 at 17:44
  • The reason GetType requires boxing is that only class objects have type information associated with them. If one calls 5.GetType(), what happens is that the system creates a heap instance of a boxed-Integer type and then returns the type of that boxed instance, rather than the type of the original integer instance. To demonstrate that the types derived from ValueType are class types rather than value types, create a `List` with some numbers in it, call `GetEnumerator()`, storing the result in a `List.Enumerator`. Copy that to another variable of the same type and two... – supercat Jan 24 '12 at 17:48
  • ...variables of type `ValueType`. Then copy one of those variables to a third, and attempt to enumerate all five variables. The first two variables will hold independent enumeration structs. The third and fourth will hold references to two different heap objects, each holding an independent enumeration struct. The fifth will hold a reference to the same instance as the fourth. – supercat Jan 24 '12 at 17:51
  • @supercat: Nice comprehensive discussion of the difference between value types and reference types, and a tip-of-the-iceberg summary of its internal implementation. Well done, but nothing new. Now how does that relate to anything in my answer? Value types clearly still have a public default constructor. – Timwi Jan 25 '12 at 20:27
  • A constructor on a real struct type does nothing that wouldn't be done implicitly, without calling the constructor, in the process of creating a class or array that contains the struct. Invoking a struct constructor via reflection creates an instance of the auto-defined wrapper class, and the process of doing that implicitly creates the struct contained within. My point was that although a struct has a "constructor", it doesn't actually create a new instance the way that a class constructor does, at least in vb.net. – supercat Jan 26 '12 at 16:38
  • There are some corner-case differences between vb.net and C# in this regard (I mostly use the former), and I can see a case for calling the zero-initializing thing C# uses a "constructor", though it's worthwhile to note the differences in vb.net's behavior. In vb.net, the statement `structVar = new structType(whatever)` will zero-fill `structVar` and then cause the code in `structType`'s "new()" method to be run upon it. In C#, the compiler would allocate a temporary instance, pass it to the "new()" method, and then copy it to the variable. – supercat Jan 26 '12 at 16:49
  • Whether you can “see a case for calling it a constructor” is irrelevant. It’s called a constructor because the specifications (both the C# spec as well as the CLR spec) clearly define it as one. (Also, your assessment of C#’s behaviour is wrong. It does the same as what you describe for VB.NET, which shouldn’t be surprising because it compiles to the same IL code.) – Timwi Jan 27 '12 at 02:36
  • Terms like "constructor", "destructor", "reference", etc. have many meanings; no point in further quibbling on that score. As for vb.net versus C#, the behavior of `structVar = new StructType(someParams)` does differ in the two languages--I've tested it. In C#, `structVar` does not get written until the constructor returns; in vb.net, it does. – supercat Jan 27 '12 at 15:24
  • Not sure what you mean by _you can discover_ a struct's public default constructor _via reflection_ (your first comment above). If you try `typeof(TimeSpan).GetConstructors()`, for example, you get a lot of constructors, but no parameterless one. And saying, more insistingly, `typeof(TimeSpan).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, Type.DefaultBinder, new Type[] { }, null)` gives you no match. So the zero-parameter constructor is not there. C#'s `new TimeSpan()` is just an (older) syntax for `default(TimeSpan)`. – Jeppe Stig Nielsen May 06 '14 at 20:58
  • @JeppeStigNielsen: Indeed. I notice there’s a *static* constructor; I probably saw that one when I wrote this claim. Thanks for the correction. – Timwi May 06 '14 at 21:51
2

Structs in a container can only be modified if the container is a built-in array:

struct Point { public int x, y; void Move(int dx, int dy) { x += dx; y += dy; } }
...
Point[] points = getPointsArray();
points[0].Move(10, 0) = 10;
// points[0].x is now 10 higher.
List<Point> points = getPointsList();
points[0].Move(10, 0);
// No error, but points[0].x hasn't changed.

For this reason, I strongly favour immutable structs:

Point Move(int dx, int dy) { return new Point(x + dx, y + dy); }
...
points[0] = points[0].Move(10, 0); // Always works.

General observation: classes are usually better. Structs excel when you want to hold small, conceptually atomic data structures such as Point, Complex (number), Rational, etc.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
  • This is true, but the reason for this might be hard to perceive at first sight, and is not actually related to arrays. Since structs are value types, indexing the list returns a copy of the actual struct, and changing it (using the `Move` method) does not change the original value. Same behaviour would be perceived if that struct was in a public property of a different class. Furthermore, `points[0] = points[0].Move(10, 0);` would "work" with a mutable struct also. – vgru May 01 '10 at 13:46
  • @Groo: It _is_ related to arrays in the sense that only arrays support mutation of struct elements. They are _privileged_, in a sense, which is something I dislike in the design of a language (C++, for all its warts and bedsores, doesn't exhibit this flaw with its containers). Certainly the alternate `Move` semantics would work with a mutable struct, but then a mutable struct typically wouldn't implement `Move` that way. – Marcelo Cantos May 01 '10 at 14:37
  • @MarceloCantos: It's too bad that .net and languages for it doesn't provide a means of exposing properties as "call-through-by-reference", such that a statement like "someCollection[5].X += 1" would translate to something like: `someCollection.ActOnItem(5, (ref item) => {item.X += 5})`. Allowing such a property to be passed by `ref` in all cases would require variadic generics, but even if support were limited to cases with no more than three `ref` parameters such transformations would still be useful. One big advantage over being able to expose properties that way is that the code which... – supercat Jul 17 '12 at 20:07
  • ...exposes the property would know when the code receiving the property was done modifying it. By contrast, if `someCollection[5]` simply returned a promiscuous object reference (the normal practice), the only way it could know that the object was modified would be for the object to include a notification callback. Such a callback would add considerable complexity even in scenarios where a simple pass-struct-by-`ref` would suffice. – supercat Jul 17 '12 at 20:13
  • @supercat: It's an interesting idea, but probably not as simple as it sounds. For instance, what would `someCollection[5].X.Z += 1` do if X is of a value type? Also, since `foo(ref array[5].X)` is allowed, would you also allow `foo(ref someCollection[5].X)`? If so, what about `foo(ref someCollection[5].X, ref someCollection[6].Y)`? – Marcelo Cantos Jul 18 '12 at 07:44
  • The general scenario would require either variadic generics or some other means of passing an arbitrary number of `ref` parameters. The latter code would be something like `var temp = new PairStruct(6, this); someCollection.ActOnElement(5, ref temp, (ref Item element, ref PairStruct param) => {someCollection.ActOnElement(param.p1, (ref Item element, ref Item otherElement) => {param.p2.foo(ref otherElement.X, ref element.Y);})});` Hopefully I got all that right. Making things work nicely would require compiler support (and, as noted, framework support for... – supercat Jul 18 '12 at 14:57
  • ...passing an arbitrary number of `ref` parameters), but the transformations required would be less complicated than those for iterators. Note also that (unless I slipped up somewhere) the lambdas are both static, and thus would not require closures. Personally, I dislike the way closures generate promiscuous delegates even in cases where the called method will in fact not persist them; I think there should be a means by which routines which aren't going to persist delegates could receive a non-persistable delegate-like thing (combo of method and params) using a construct like the above. – supercat Jul 18 '12 at 15:01
  • Incidentally, even when using heap objects, I wish there were a means by which routine arguments of class-reference types could be declared as promiscuous references (the present and default behavior), ephemeral references (which the routine could not persist, nor return, nor pass as a promiscuous parameter), or returnable references (like the above, but they could be returned; when calling a method which took returnable arguments, one could only do with the return value things one could do with the most restricted of the returnable parameters passed to it. – supercat Jul 18 '12 at 15:05
  • Unfortunately, I don't see any way to retrofit the Framework to include any ephemeral types other than `ref`; allowing `ref` types to be returned, but with the caveat that a routine could only return a `ref` that was passed into it if its argument was declared as "returnable ref", might be a more workable retrofit. Still, I think a "call-through" concept like I illustrated above might in some ways be even better with the proper compiler/framework support, since it lets the owner of an object know when someone is done with it. – supercat Jul 18 '12 at 15:09
1

structs, as they are value types, are copied on assignment; if you create your own struct, you should make it immutable, see Why are mutable structs evil?

Community
  • 1
  • 1
gammelgul
  • 3,450
  • 1
  • 20
  • 16
1

Sometimes you don't want what you're passing to be mutable, and since a mutable struct may just be pure evil, I'd steer clear of ever creating one :) Here's an example a situation:

class Version:

class AccountInfo {
   public string OwnerName { get; set; }
   public string AccountNumber { get; set; }
}

struct Version:

struct AccountInfo {
   public string OwnerName;
   public string AccountNumber;
}

Now picture you called a method like this:

public bool TransferMoney(AccountInfo from, AccountInfo to, decimal amount)
{
   if(!IsAuthorized(from)) return false;
   //Transfer money
}

A struct is a Value type, meaning a copy gets passed into the method. The class version means a reference gets passed into the method, you wouldn't want for example the account number to be changeable after the authorization passed, you want nothing to be changed in an operation like this...you want an immutable value type. There's another question here asking why mutable structs are evil...any operation where you wouldn't want anything affected by the reference object changing, would be a practical place where a struct may fit better.

The example above may be somewhat silly, but the point is any sensitive operation where the passed in data shouldn't change in another thread or by any means really would be a place you look at passing by value.

Community
  • 1
  • 1
Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
  • @Gulshan - Yes they do work, just old habit writing them like above. I prefer a `struct` to me immediately recognizable inside and out when coding. – Nick Craver May 01 '10 at 13:56
0

Where they are allocated (heap vs. stack) is not something you really care about while you use them (not that you should disregard this - you should by all means study the differences and understand them).

But the most important practical difference you will come across the first time you decide to replace your class with a struct, is that structs are passed by value, while class instances are passed by reference.

This means that when you pass a struct to a method, a copy of its properties is created (a shallow copy) and your method actually gets a different copy than the one you had outside the method. When you pass an instance of a class, only a reference to the same place in memory is passed to the method, and your method is then dealing with exactly the same data.

For example, if you have a struct named MyStruct, and a class named MyClass, and you pass them to this method:

 void DoSomething(MyStruct str, MyClass cls)
 {
      // this will change the copy of str, but changes
      // will not be made to the outside struct
      str.Something = str.Something + 1;

      // this will change the actual class outside
      // the method, because cls points to the
      // same instance in memory
      cls.Something = cls.Something + 1;
 }

when the method ends, your class' property will be incremented, but your struct's property will remain unchanged, because str variable inside the DoSomething method does not point to the same place in memory.

vgru
  • 49,838
  • 16
  • 120
  • 201
0

The singularly important practical difference is that structs are value types, whereas classes are reference types. That has a few implications.

First of all, structs are copied on assignment. These two code blocks will have a different result (please note, normally you should neither use public fields nor mutable structs, I'm doing this for demonstration purposes only):

struct X
{
    public int ID;
    public string Name;
}

X x1 = new X { ID = 1, Name = "Foo" };
X x2 = x1;
x2.Name = "Bar";
Console.WriteLine(x1.Name);    // Will print "Foo"

class Y
{
    public int ID;
    public string Name;
}
Y y1 = new Y { ID = 2, Name = "Bar" };
Y y2 = y1;
y2.Name = "Baz";
Console.WriteLine(y1.Name);    // Will print "Baz"

X and Y are exactly the same, except that X is a struct. The results of this are different because every time we assign an X, a copy is made, and if we change the copy then we aren't changing the original. On the other hand, when we assign the contents of y1 to y2, all we've done is copied a reference; both y1 and y2 refer to physically the same object in memory.

The second consequence of structs being value types is generic constraints. If you want to pass in value types, the name of the constraint is literally "struct" or "class":

public class MyGeneric<T>
    where T : struct
{ ... }

The above will let you create a MyGeneric<int> or MyGeneric<X>, but not a MyGeneric<Y>. On the other hand, if we change it to where T : struct, we're no longer allowed to create either of the first two, but MyGeneric<Y> is okay.

Last but not least, you need to use structs when writing interop, because with structs you're able to guarantee a specific arrangement in memory.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
0

The link Tejs provided (http://www.jaggersoft.com/pubs/StructsVsClasses.htm) is a good explanation (although it is a bit out of date, particularly on the explanation of events).

The most import practical difference is that a struct is a value type, meaning it is passed by value rather than by reference. What this really means is that when a struct is passed as an argument, it is actually passed by copy. As a result, operations on one instance of a struct do not affect other instances.

Consider the following code:

struct NumberStruct
{
   public int Value;
}

class NumberClass
{
   public int Value = 0;
}

class Test
{
   static void Main() 
   {
      NumberStruct ns1 = new NumberStruct();
      NumberStruct ns2 = ns1;
      ns2.Value = 42;

      NumberClass nc1 = new NumberClass();
      NumberClass nc2 = nc1;
      nc2.Value = 42;

      Console.WriteLine("Struct: {0}, {1}", ns1.Value, ns2.Value);
      Console.WriteLine("Class: {0}, {1}", nc1.Value, nc2.Value);
   }
}

Because both ns1 and ns2 are of the NumberStruct value type, they each have their own storage location, so the assignment of ns2.Number does not affect the value of ns1.Number. However, because nc1 and nc2 are both reference types, the assignment of nc2.Number does affect the value of nc1.Number because they both contain the same reference.

[Disclaimer: The above code and text taken from Sams Teach Yourself Visual C# 2010 in 24 Hours]

Also, as others have already pointed out, structs should always be immutable. (Yes, in this example the struct is mutable but it was to illustrate the point.) Part of that means that structs should not contain public fields.

Since structs are value types, you cannot inherit from a struct. You also cannot derive a struct from a base class. (A struct can implement interfaces, however.)

A struct is also not allowed to have an explicitly declared public default (parameterless) contstructor. Any additional constructors you declare must completely initialize all of the struct fields. Structs also cannot have an explicitly declared destructor.

Since structs are value types, they shouldn't implement IDisposable and shouldn't contain unmanaged code.

Scott Dorman
  • 42,236
  • 12
  • 79
  • 110
-1

Here's an interesting link: http://www.jaggersoft.com/pubs/StructsVsClasses.htm

For the most part though, there isn't much of a compelling reason to use structs when classes offer far more to the developer.

Tejs
  • 40,736
  • 10
  • 68
  • 86
  • -1: While the reference may be good, some explanations and code examples would be preferable. If that link goes away then this answer is meaningless. – IAbstract May 01 '10 at 14:15
  • -1 for the blanket statement that makes it sound like structs are completely useless and should never be used. – Timwi Sep 24 '10 at 13:55
  • Sometimes code needs a bunch of variables stuck together with duct tape. A class can kinda sorta be used as a bunch of variables stuck together with duct tape, but not very well. By contrast, a struct *is* a bunch of variables stuck together with duct tape, and is thus a perfect fit in cases where one is needed. – supercat Sep 02 '14 at 12:41