2

Please check out these two small examples:

public struct Struct
{
    public readonly string StringValue;
    public readonly int IntValue;

    public Struct(string stringValue)
    {
        this.StringValue = stringValue;
        this.IntValue = this.StringValue.GetHashCode(); // Just ignore this assignment, please :).
    }
}

public class Class
{
    public readonly string StringValue;
    public readonly int IntValue;

    public Class(string stringValue)
    {
        this.StringValue = stringValue;
        this.IntValue = this.StringValue.GetHashCode(); // Just ignore this assignment, please :).
    }
}

From what I know if I pass struct as a parameter it will make a copy of it someObject.Method(this.cachedStruct); - which in turn will make a copy of a StringValue as strings are immutable it will allocate array in memory for this string which is quite costly operation [pardon me if I made a mistake, please correct me in such a case].

So in this instance I assume it will be faster to use instances of the Class rather than copying StringValue each time. QUESTION: Are my assumptions correct and Class is a better choice for this kind of a situation or is it still more beneficial and faster to use Struct?

Since String and string are the same. ref - What is the difference between String and string in C#?. I cannot use String in struct to prevent it from creating a new string object when struct has been passed as a parameter.

To better address the issue, here is another example. Sorry if it's a bit rough example.

public struct Struct
{
    public string StringValue;
    public int IntValue;

    public Struct(string stringValue)
    {
        this.StringValue = stringValue;
        this.IntValue = this.StringValue.GetHashCode(); // Just ignore this assignment, please :).
    }
}

public class Class
{
    public string StringValue;
    public int IntValue;

    private Struct _niceStruct;

    public void Accept(Struct someStruct)
    {
        someStruct.StringValue = "Something new"; // In this case even if I don't change the value, it was already allocated.
        // Because of copying of struct when it was passed as a parameter.

        Console.WriteLine(this._niceStruct.StringValue); // "Something new".
    }

    public Class(string stringValue)
    {
        this.StringValue = stringValue;
        this.IntValue = this.StringValue.GetHashCode(); // Just ignore this assignment, please :).

        this._niceStruct = new Struct("Old value");

        this.Accept(this._niceStruct);

        Console.WriteLine(this._niceStruct.StringValue); // "Old value".

        // String value was left unaffected.
        // Which [even in the case I wouldn't change the value in the method] means that a new `string` object was allocated when a copy of `Struct` was created.
        // When using `Class` in the same manner - it would change the `string`.
        // New `string` object won't be allocated if value is unchanged.

        // I assume that `string` allocation might be more costly than `Class` allocation in this instance.
    }
}

My understanding is that if I use Struct without changing string value it will still allocate memory for StringValue each time I pass it as a parameter. While when using Class and passing its instance as a parameter - it will pass Class instance as a reference type without copying values, thus not allocating any memory for StringValue.

My question might seem similar to C#, struct vs class, faster? [duplicate] and other questions (I believe I have gone through them all (the ones that struct vs class performance)) but none of them have given me an answer.

  • 5
    That is not how it works. System.String is a reference type, you only copy the reference and not the string. Very fast, references take 4 or 8 bytes. Having two references to one string object is not a problem at all, given that it is immutable. So you're fretting over a non-existing problem. – Hans Passant Jan 01 '20 at 22:46
  • @HansPassant but in case `Struct` isn't immutable and I change `StringValue` after I have passed instance as a parameter, wouldn't changes only apply to one string, while the other would be left unaffected, retaining it's previous value? – Candid Moon _Max_ Jan 01 '20 at 22:49
  • 1
    You are changing the copy of the struct, not the string. Otherwise the reason behind the common recommendation to never mutate a struct. It is fine to do so, but programmers tend to have trouble reasoning through the consequences. – Hans Passant Jan 01 '20 at 22:53
  • @HansPassant I have updated the question trying to better address the issue. Please, have a look at it. – Candid Moon _Max_ Jan 01 '20 at 23:06
  • It is as I said, the recommendation is to not mutate a struct. But you're mutating a struct and are surprised that modifying the copy did not also modify the original. Declare it as a class instead and it works the way you hope it did. Or change the Accept() method parameter to `ref Struct` so it no longer operates on a copy. Which is fine, but not efficient. Structs were added to the C# language to make code faster. They are a good match for the way a processor works. – Hans Passant Jan 01 '20 at 23:14
  • @HansPassant I have an understanding between differences of reference and value types. I am not surprised that the value was changed. I want to understand if passing `struct` as a parameter is more costly than passing a `class` in case `struct` contains a `string` value. Does it mean if struct is immutable - compiler passes it as `ref` instead of copying it? Thus improving performance. – Candid Moon _Max_ Jan 01 '20 at 23:18
  • 3
    No, only using *ref* on the parameter declaration is inefficient. Processors much prefer to work with copies and not references, they like a copy in their internal registers and hate to have to address slow memory through a reference. There is no easy syntax to make a struct immutable. You have to make the fields private and use properties that don't have a setter. The C# language was designed to make things you shouldn't do harder to do. – Hans Passant Jan 01 '20 at 23:27
  • @HansPassant now I understand what you have meant in the first comment. Sorry, I got confused and haven't understood `given that it is immutable` part right away. – Candid Moon _Max_ Jan 01 '20 at 23:35

2 Answers2

5

The string is for example at address 0x0110 and called "hello". The struct does not store the string itself, it only stores the reference to this string. Thus the struct will only store "0x0110". When you copy the struct, the struct copy will store the same reference "0x0110". Not the string, just the reference. No new string object will be created. But when you change the string in the struct copy, for example from "hello" to "bla", a new string will be created at a new address. For example "bla" will be at address 0x0220. the struct copy will then store "0x0220" instead of "0x0110". The original struct will still store "0x0110". You are now having 2 strings in memory at different adresses.

0x0110 "hello"
0x0220 "bla"

PS: that is a simplification of what really happens.

Blechdose
  • 1,748
  • 15
  • 24
0

You seem to not know exactly how reference and value types work.

You have variable aof a type that is either class or a struct. This variable holds a value, thats all a variable is. What the value is depends one whether the type is a reference type or a value type:

  • If its a reference type, the value of a is a reference, that is, a memory address where the referenced object is found.
  • If its a value type, the value of a is the value type itself.

In C#, by default, variables are passed by value. That is, a copy of the variable's value is made:

SomeType a = new SomeType();
var b = a;
SomeMethod(b)

void SomeMethod(SomeType t) { }

b holds a copy of a. And t holds a copy of b.

If SomeType is a reference type (for example string) then all three variables a, b and t hold the same value; an address to the string instance originally assigned to a.

If SomeType is a value type, then all three variables hold three identical but distinct instances of the value type returned by new SomeType(). The variables that make up the inner state of said value type are themselves copied in the same manner: their value is copied.

Does that make it clearer?

InBetween
  • 30,991
  • 3
  • 46
  • 80