0

From the official docs:

AVOID defining a struct unless the type has all of the following characteristics:
[...]
It has an instance size under 16 bytes.
[...]

I know that one reason for keeping it under 16 bytes is that structs are intended to be used on the stack. However, are there other reasons?

In order to not accidentally create an XY problem, what I want to know is:
Does this 16 byte rule still make sense in a situation where I use a struct as a private field in a class?

We all love code, so here's an example:

public struct Address
{
    public readonly string Line1;
    public readonly string Line2;

    public Address(string line1, string line2)
    {
        Line1 = line1;
        Line2 = line2;
    }

}

public class Person
{
    private Address _address;

    [...]

    public void ChangeAddressLine1(string newLine1)
    {
        _address = new Address(newLine1, _address.Line2);
    }
}
  • 1
    Are we still trying to use structs as DTO's? :P – Robert Harvey Dec 29 '17 at 22:43
  • 3
    Your Adress struct actually requires a size of 16 bytes in x64 mode, and 8 bytes in x86 mode, not more. String members are nothing but references to string objects, and references are implemented in the CLI as pointers. – Doc Brown Dec 29 '17 at 23:21
  • @Robert Harvey Nah, I'll probably never get to that. Just trying to make sense of why structs are even there or if they have any role in Clean Code. – R. Schmitz Dec 31 '17 at 11:16
  • @Doc Brown My mistake for not mentioning I'm working in x64. However, take note that the docs say "under 16 bytes" - so according to that a struct can't even handle 2 strings . – R. Schmitz Dec 31 '17 at 11:16
  • @RaphaelSchmitz: huh? You didn't take that 16 bytes recommendation (not more!) literally, don't you? This is a soft limit, surely you can make structs bigger than that if you like, it is just a matter of performance and resources, which for 99,9% of all real world programs won't matter. And if a struct need 15, 16 or 24 bytes does not make a big difference for most situations. – Doc Brown Dec 31 '17 at 15:31
6

You should prefer small structs because structs will likely be copied, and smaller structs are cheaper to copy.

The docs do provide a bit more context for that 16-byte size limit:

Next, reference type assignments copy the reference, whereas value type assignments copy the entire value. Therefore, assignments of large reference types are cheaper than assignments of large value types.

Assuming a word size of 8 bytes, a 16-byte struct is only twice as large as a pointer to a reference type, thus still comparably efficient to copy.

This is completely unrelated to limited stack size. The stack typically has far more space than you might think, unless you're writing deeply recursive algorithms.

In C and C++, it is common to store significant amounts of data on the stack, and these languages only have value types. But unlike C# they can specify whether a struct is passed by value (and thus copied), or by reference or pointer (without a copy). The C++ Core Guidelines recommend that parameters are passed by reference, unless the type is known to be be efficiently copyable (trivially copyable without invoking copy constructors, and only up to two or three words large). Under those circumstances, a copy is more efficient than passing by reference because the copy avoids pointer indirection.

  • Can you make it a little clearer why you went into such great detail about C++ in the last paragraph, and how that relates to C# specifically? – Robert Harvey Dec 29 '17 at 22:50
  • 3
    @RobertHarvey When a value type should be copied does not depend very much on the language. So looking at the conventions of a language where copies of value types are far more common than in C# is useful. In particular, the C++ conventions are well-documented and in this case provide a transferable insight. – amon Dec 29 '17 at 22:55
  • C# can pass structs by both value and reference. For example, passing by reference is mandatory for the SpinWait structure. – IamIC Dec 31 '17 at 6:57
  • 1
    @IamIC You're right! Especially with C# 7 ref locals, unnecessary struct copies can be avoided. But this requires users to know whether a type is a struct or a class. One recommendation is to make all structs immutable. Because immutability makes copies un-observable, it doesn't matter whether a type is implemented as a struct or a class. Once a struct grows it can be replaced by an equivalent reference type. – amon Dec 31 '17 at 10:35
  • I chose this answer because it mentions - in a pragmatic way - why limiting the size would still make sense in the given situation. – R. Schmitz Dec 31 '17 at 12:03
2

I know that one reason for keeping it under 16 bytes is that structs are intended to be used on the stack.

The manner in which structs are stored in C# is an implementation detail. They might be stored on the stack, but they might not.

Does this 16 byte rule still make sense in a situation where I use a struct as a private field in a class?

What matters is whether you want value or reference semantics, not the absolute size of the struct. If you're willing to put up with the copying, your structs can be as large as you want. In most cases, however, you're going to be better served by using classes for larger data types.

Further Reading
The Truth About Value Types

  • I appreciate your effort, but this is the second time where I have a question with "struct" in the title where you answer the question "When should I use a struct". I wanted to understand the 16 byte rule from the docs. You do mention in one sub clause that it doesn't matter without any explanation - but that seems more like an accidental byproduct of answering your favorite question. – R. Schmitz Dec 31 '17 at 11:54
  • Actually, if you read my answer carefully, I think you'll find that I'm merely debunking some of your incorrect assumptions. The 16 byte rule is there to discourage folks like you from using value semantics for everything. – Robert Harvey Dec 31 '17 at 19:50
  • You didn't debunk any of those quotes, you barely touch their subjects. I guess "folks like me" are people who don't know as much about structs yet? Well, I'm trying to learn more about them. That's why I'm asking these questions. It's not only me though - do you think anybody seeking answer to these questions and landing here is going to be satisfied with the answer "It doesn't matter, [When to use a struct]"? – R. Schmitz Jan 1 '18 at 14:11
  • I don't know why you're being so hostile, @Raphael. Part of understanding is starting with the right assumptions, and since the fundamental assumption in your question that "structs are intended to be used on the stack" is patently incorrect, doesn't that sort of invalidate part or all of your question? Part of the learning process is admitting when your wrong. – Robert Harvey Jan 2 '18 at 1:09
  • I'm trying to find the hostility in my comment. I even took you and me out of the equation as to not hurt any feelings: If ANYBODY searches this question and reads an answer by ANYBODY that says "It doesn't matter, [When to use a struct]", that answer is just not helping them in their learning effort. – R. Schmitz Jan 2 '18 at 14:01
1

I think it depends on how you access/use the struct that is the private member of the object.

In an access of the struct member from the object, immediately followed by a struct member access, the IL will directly follow these steps making it appear to obtain the struct and then from that the member of the struct.

However, the JIT will interpret this as addressing calculations ultimately intended to access just the member of the struct. Despite the IL instruction to do so, it will not load/copy the whole struct (e.g. to the stack) in order to access one field of the struct. Thus, the JIT should generally produce the same code to access person._address.Line1 as for person.Line1, e.g. as if the Line1/Line2 fields were directly in the Person object.

However, if you access the struct as a whole without immediately accessing a struct member, it will probably make a copy to work with; sometimes that is strictly necessary but other times might have been avoided.

Handling of structs (such as optimizing copying in JIT'ed code) is an area of ongoing improvement in the .NET JIT.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.