🔒 Locked
[.net] Classes vs Structs
Advertisement
Original Post
Hi, guys. I'm using C# and, coming from the background of C++ (classes=structs) and Java (no structs), can't really grasp the difference between classes and structs. Now I know that classes are pass-by-ref and structs are pass-by-value, but what does this essentially mean? I KINDA understand, but not exactly. Could someone maybe give me situations in which a struct will be treated differently than a class? Maybe that will help me understand the difference. Thanks in advance.
'Pass by value' means that the object is copied when passed to the function. Any modifications to the local copy will not affect the object on which it was based. 'Pass by reference' means that a reference is passed, meaning any changes to the local referenced object will affect the original object you were passing.
or at least that how I understand it.
or at least that how I understand it.
Yes, a struct is allocated on the stack but a class is allocated on the heap. Much like in C++ when you instantiate a class by "SomeClass sc;" vs "SomeClass *sc = new SomeClass();". It is more efficient than a class when it's smaller than certain size. The following is a direct quote from MSDN:
"The struct type is suitable for representing lightweight objects such as Point, Rectangle, and Color. Although it is possible to represent a point as a class, a struct is more efficient in some scenarios. For example, if you declare an array of 1000 Point objects, you will allocate additional memory for referencing each object. In this case, the struct is less expensive.
It is an error to declare a default (parameterless) constructor for a struct. A default constructor is always provided to initialize the struct members to their default values.
It is an error to initialize an instance field in a struct.
When you create a struct object using the new operator, it gets created and the appropriate constructor is called. Unlike classes, structs can be instantiated without using the new operator. If you do not use new, the fields will remain unassigned and the object cannot be used until all of the fields are initialized.
There is no inheritance for structs as there is for classes. A struct cannot inherit from another struct or class, and it cannot be the base of a class. Structs, however, inherit from the base class Object. A struct can implement interfaces, and it does that exactly as classes do.
Unlike C++, you cannot declare a class using the keyword struct. In C#, classes and structs are semantically different. A struct is a value type, while a class is a reference type. For more information on the features of value types, see Value Types.
Unless you need reference type semantics, a class that is smaller than 16 bytes may be more efficiently handled by the system as a struct."
For your other question about the ref keyword, it's basically the same as the reference operator & in C++.
"The struct type is suitable for representing lightweight objects such as Point, Rectangle, and Color. Although it is possible to represent a point as a class, a struct is more efficient in some scenarios. For example, if you declare an array of 1000 Point objects, you will allocate additional memory for referencing each object. In this case, the struct is less expensive.
It is an error to declare a default (parameterless) constructor for a struct. A default constructor is always provided to initialize the struct members to their default values.
It is an error to initialize an instance field in a struct.
When you create a struct object using the new operator, it gets created and the appropriate constructor is called. Unlike classes, structs can be instantiated without using the new operator. If you do not use new, the fields will remain unassigned and the object cannot be used until all of the fields are initialized.
There is no inheritance for structs as there is for classes. A struct cannot inherit from another struct or class, and it cannot be the base of a class. Structs, however, inherit from the base class Object. A struct can implement interfaces, and it does that exactly as classes do.
Unlike C++, you cannot declare a class using the keyword struct. In C#, classes and structs are semantically different. A struct is a value type, while a class is a reference type. For more information on the features of value types, see Value Types.
Unless you need reference type semantics, a class that is smaller than 16 bytes may be more efficiently handled by the system as a struct."
For your other question about the ref keyword, it's basically the same as the reference operator & in C++.
When you pass a parameter by value it the value of the parameter is copied to the stack of the function.
- Thus when the parameter is a value type; the function can do to the parameter whatever it wants, this will not affect the original value in the calling function.
- When the parameter is a reference type; the function can change the object because the value that was copied to the stackframe of the function is a reference to the object that is on the heap.
When you pass a parameter by reference (ref) the stackframe of the function that is called will contain an indirection to the original value.
- So when the parameter is a value type; the function can change the original value. That is why you have to add ref when calling the function; this way you can see that the parameter might be changed.
- When passing a parameter that is a reference type, you actually pass a reference to a reference to an object. So you can change the original reference! IOW: the original reference might point to a new or other object after the function has completed it's work.
PS: out is the same as ref but you do not need to initialize the variable before passing it to the function. The compiler expects the function to do the initialization of the parameter.
PPS: Many times Reference types are refered to as objects on the heap and structs on the stack. This is not entirely true. Local variables of value types are allocated on the stack BUT member variables will be inlined on the heap as part of the object they are part of.
PPPS: In answer to your question: variables allocated on the stack are faster accessed that objects on the heap. This is because of the extra indirection that the Garbage Collector imposes (no pointers but references). That is why, in general, very simple types (Complex numbers, Vectors, Matrices etc) are value types (structs). However as soon as such an object is part of a refererence type you lose most of the advantage because you need to follow at least one indirection to the heap. Also: you can't inherit from value types and you can't define a default contructor for them. You also have to be very careful because value types have a tendency to copy more often than you expect them to do. Do read about boxing when memory and speed are essential to you. In short when you pass a value type where a reference to an object is expected, the CLR creates a box (reference type) around your value type that contains the value and it's type to be able to cast it back again. This can and will be very costly. E.g.: when storing Vectors in an ArrayList.
Cheers
(My goodness this must be my longest post ever...)
- Thus when the parameter is a value type; the function can do to the parameter whatever it wants, this will not affect the original value in the calling function.
- When the parameter is a reference type; the function can change the object because the value that was copied to the stackframe of the function is a reference to the object that is on the heap.
When you pass a parameter by reference (ref) the stackframe of the function that is called will contain an indirection to the original value.
- So when the parameter is a value type; the function can change the original value. That is why you have to add ref when calling the function; this way you can see that the parameter might be changed.
- When passing a parameter that is a reference type, you actually pass a reference to a reference to an object. So you can change the original reference! IOW: the original reference might point to a new or other object after the function has completed it's work.
PS: out is the same as ref but you do not need to initialize the variable before passing it to the function. The compiler expects the function to do the initialization of the parameter.
PPS: Many times Reference types are refered to as objects on the heap and structs on the stack. This is not entirely true. Local variables of value types are allocated on the stack BUT member variables will be inlined on the heap as part of the object they are part of.
PPPS: In answer to your question: variables allocated on the stack are faster accessed that objects on the heap. This is because of the extra indirection that the Garbage Collector imposes (no pointers but references). That is why, in general, very simple types (Complex numbers, Vectors, Matrices etc) are value types (structs). However as soon as such an object is part of a refererence type you lose most of the advantage because you need to follow at least one indirection to the heap. Also: you can't inherit from value types and you can't define a default contructor for them. You also have to be very careful because value types have a tendency to copy more often than you expect them to do. Do read about boxing when memory and speed are essential to you. In short when you pass a value type where a reference to an object is expected, the CLR creates a box (reference type) around your value type that contains the value and it's type to be able to cast it back again. This can and will be very costly. E.g.: when storing Vectors in an ArrayList.
Cheers
(My goodness this must be my longest post ever...)
The implicit boxing mechanism mentioned above is indeed a double-edged sword. While it allows simpler programming mindless use of structs can have severe impact on speed and/or memory usage.
Examples for implicit boxing:
- assignment to an Object-variable (as mentioned above). This silently happens e.g. in String.Format or Equals if there is no override expecting exactly that valuetype. Solutions:
a) prefer
b) provide a specialized version of Equal whenever possible.
- calling any of the following Methods IF THEY ARE NOT OVERRIDEN(!!): ToString, Equals, GetHashCode. These are virtual methods of class Object. As a valuetype does not have an implicit vtable-pointer, it needs to be boxed for the call. However, if you override the method and call it through a value-type the call does not have to be evaluated virtually, as valuetypes can never be base-classes and so the methods cannot be further overriden by an eventual derived class. Logical consequence: it is desirable to override these methods in structs.
- calling GetType. This *always* boxes the value-type. GetType() is not virtual and thus cannot be overriden in the ValueType
- passing a valuetype implementing an interface to a function expecting that interface. At the first view, that sounds weird, but it is actually logical: e.g. an interface-variable can be null, value-types can't so they have to be boxed.
Example:
Compile the code and look at it with ildasm or Reflector. You will see the box-opcodes at the points mentioned. (In the example code I always create new instances of the value types as the compiler would cache the boxed objects otherwise and it would not become clear where implicit boxing occurs).
Regards,
Andre
P.S.: who said .NET was simple? :)
Examples for implicit boxing:
- assignment to an Object-variable (as mentioned above). This silently happens e.g. in String.Format or Equals if there is no override expecting exactly that valuetype. Solutions:
a) prefer
String.Format("{0} {1}", myValueType.ToString(), otherValueType.ToString()) over the implicit String.Format("{0} {1}", myValueType, otherValueType) (ToString must be overriden in the valuetype, though - see next example of implicit boxing)b) provide a specialized version of Equal whenever possible.
- calling any of the following Methods IF THEY ARE NOT OVERRIDEN(!!): ToString, Equals, GetHashCode. These are virtual methods of class Object. As a valuetype does not have an implicit vtable-pointer, it needs to be boxed for the call. However, if you override the method and call it through a value-type the call does not have to be evaluated virtually, as valuetypes can never be base-classes and so the methods cannot be further overriden by an eventual derived class. Logical consequence: it is desirable to override these methods in structs.
- calling GetType. This *always* boxes the value-type. GetType() is not virtual and thus cannot be overriden in the ValueType
- passing a valuetype implementing an interface to a function expecting that interface. At the first view, that sounds weird, but it is actually logical: e.g. an interface-variable can be null, value-types can't so they have to be boxed.
Example:
interface IMyInterface {}/// <summary>/// This struct overrides the virtual methods of Object/// </summary>struct MyStruct : IMyInterface { public override bool Equals( object obj ) { return false; } public override int GetHashCode() { return 0; } public override string ToString() { return ""; }}/// <summary>/// This struct does not override the virtual methods of Object/// </summary>struct MyStruct2 : IMyInterface {}class Program { static void Foo(IMyInterface var) { } static void Main() { MyStruct s1; s1 = new MyStruct(); s1.Equals( null ); // no boxing s1 = new MyStruct(); s1.ToString(); // no boxing s1 = new MyStruct(); s1.GetHashCode(); // no boxing s1 = new MyStruct(); s1.GetType(); // implicit boxing Foo( s1 );// implicit boxing MyStruct2 s2; s2 = new MyStruct2(); s2.Equals( null ); // implicit boxing s2 = new MyStruct2(); s2.ToString(); // implicit boxing s2 = new MyStruct2(); s2.GetHashCode(); // implicit boxing s2 = new MyStruct2(); s2.GetType(); // implicit boxing Foo( s2 );// implicit boxing }}Compile the code and look at it with ildasm or Reflector. You will see the box-opcodes at the points mentioned. (In the example code I always create new instances of the value types as the compiler would cache the boxed objects otherwise and it would not become clear where implicit boxing occurs).
Regards,
Andre
P.S.: who said .NET was simple? :)
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement