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); } } 
9
  • 1
    Are we still trying to use structs as DTO's? :P Commented Dec 29, 2017 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. Commented Dec 29, 2017 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. Commented Dec 31, 2017 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 . Commented Dec 31, 2017 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. Commented Dec 31, 2017 at 15:31

3 Answers 3

7

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.

5
  • 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? Commented Dec 29, 2017 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. Commented Dec 29, 2017 at 22:55
  • C# can pass structs by both value and reference. For example, passing by reference is mandatory for the SpinWait structure. Commented Dec 31, 2017 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. Commented Dec 31, 2017 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. Commented Dec 31, 2017 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

7
  • 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. Commented Dec 31, 2017 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. Commented Dec 31, 2017 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]"? Commented Jan 1, 2018 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. Commented Jan 2, 2018 at 1:09
  • 1
    Well, I don't mean to be hostile, just as you probably are not meaning to be nitpicky. Yes, I am wrong because structs are not literally intended to be used on the stack, per the design document. But does that information help here? In theory, the theory is always right, but when I click "compile", I end up with structs on the stack and have to work with that. Commented Jan 2, 2018 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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.