Readonly struct vs classes
Clash Royale CLAN TAG#URR8PPP
up vote
22
down vote
favorite
Assuming you only have immutable types
and you have all your code up to date to C# 7.3 and your methods are using the in
keyword for inputs
Why would you ever use a class instead of a readonly struct?
The downside of using structs was that copying was expensive, but assuming you prevent any copy (defensive compiler copy or expressed by code), readonly structs allow you to only copy the reference (as classes do) and avoid heap allocation and pressure on the garbage collector.
Excluding special cases (which I guess it could be a very large object that won't fit on the stack) would you use readonly struct as first choice normally?
The case I am interested in is where they are used as data containers.
c# .net
 |Â
show 5 more comments
up vote
22
down vote
favorite
Assuming you only have immutable types
and you have all your code up to date to C# 7.3 and your methods are using the in
keyword for inputs
Why would you ever use a class instead of a readonly struct?
The downside of using structs was that copying was expensive, but assuming you prevent any copy (defensive compiler copy or expressed by code), readonly structs allow you to only copy the reference (as classes do) and avoid heap allocation and pressure on the garbage collector.
Excluding special cases (which I guess it could be a very large object that won't fit on the stack) would you use readonly struct as first choice normally?
The case I am interested in is where they are used as data containers.
c# .net
1
Because you want to access and edit the same object by reference?
â MineR
Aug 14 at 8:09
10
Pickstruct
orclass
, first and foremost based on whether you want value semantics or reference semantics.
â Damien_The_Unbeliever
Aug 14 at 8:11
Also no inheritance.
â MineR
Aug 14 at 8:14
3
You've taken limited range of use-cases, and tried to ask generic question, based on them. Reference types are not data containers only.
â Dennis
Aug 14 at 8:20
1
@jrh - yep, they will behave that way in a lot of ways. I can still pick 'em apart with good oldReferenceEquals
though :-)
â Damien_The_Unbeliever
Aug 14 at 12:27
 |Â
show 5 more comments
up vote
22
down vote
favorite
up vote
22
down vote
favorite
Assuming you only have immutable types
and you have all your code up to date to C# 7.3 and your methods are using the in
keyword for inputs
Why would you ever use a class instead of a readonly struct?
The downside of using structs was that copying was expensive, but assuming you prevent any copy (defensive compiler copy or expressed by code), readonly structs allow you to only copy the reference (as classes do) and avoid heap allocation and pressure on the garbage collector.
Excluding special cases (which I guess it could be a very large object that won't fit on the stack) would you use readonly struct as first choice normally?
The case I am interested in is where they are used as data containers.
c# .net
Assuming you only have immutable types
and you have all your code up to date to C# 7.3 and your methods are using the in
keyword for inputs
Why would you ever use a class instead of a readonly struct?
The downside of using structs was that copying was expensive, but assuming you prevent any copy (defensive compiler copy or expressed by code), readonly structs allow you to only copy the reference (as classes do) and avoid heap allocation and pressure on the garbage collector.
Excluding special cases (which I guess it could be a very large object that won't fit on the stack) would you use readonly struct as first choice normally?
The case I am interested in is where they are used as data containers.
c# .net
edited Aug 14 at 9:13
asked Aug 14 at 8:07
user4388177
7651417
7651417
1
Because you want to access and edit the same object by reference?
â MineR
Aug 14 at 8:09
10
Pickstruct
orclass
, first and foremost based on whether you want value semantics or reference semantics.
â Damien_The_Unbeliever
Aug 14 at 8:11
Also no inheritance.
â MineR
Aug 14 at 8:14
3
You've taken limited range of use-cases, and tried to ask generic question, based on them. Reference types are not data containers only.
â Dennis
Aug 14 at 8:20
1
@jrh - yep, they will behave that way in a lot of ways. I can still pick 'em apart with good oldReferenceEquals
though :-)
â Damien_The_Unbeliever
Aug 14 at 12:27
 |Â
show 5 more comments
1
Because you want to access and edit the same object by reference?
â MineR
Aug 14 at 8:09
10
Pickstruct
orclass
, first and foremost based on whether you want value semantics or reference semantics.
â Damien_The_Unbeliever
Aug 14 at 8:11
Also no inheritance.
â MineR
Aug 14 at 8:14
3
You've taken limited range of use-cases, and tried to ask generic question, based on them. Reference types are not data containers only.
â Dennis
Aug 14 at 8:20
1
@jrh - yep, they will behave that way in a lot of ways. I can still pick 'em apart with good oldReferenceEquals
though :-)
â Damien_The_Unbeliever
Aug 14 at 12:27
1
1
Because you want to access and edit the same object by reference?
â MineR
Aug 14 at 8:09
Because you want to access and edit the same object by reference?
â MineR
Aug 14 at 8:09
10
10
Pick
struct
or class
, first and foremost based on whether you want value semantics or reference semantics.â Damien_The_Unbeliever
Aug 14 at 8:11
Pick
struct
or class
, first and foremost based on whether you want value semantics or reference semantics.â Damien_The_Unbeliever
Aug 14 at 8:11
Also no inheritance.
â MineR
Aug 14 at 8:14
Also no inheritance.
â MineR
Aug 14 at 8:14
3
3
You've taken limited range of use-cases, and tried to ask generic question, based on them. Reference types are not data containers only.
â Dennis
Aug 14 at 8:20
You've taken limited range of use-cases, and tried to ask generic question, based on them. Reference types are not data containers only.
â Dennis
Aug 14 at 8:20
1
1
@jrh - yep, they will behave that way in a lot of ways. I can still pick 'em apart with good old
ReferenceEquals
though :-)â Damien_The_Unbeliever
Aug 14 at 12:27
@jrh - yep, they will behave that way in a lot of ways. I can still pick 'em apart with good old
ReferenceEquals
though :-)â Damien_The_Unbeliever
Aug 14 at 12:27
 |Â
show 5 more comments
1 Answer
1
active
oldest
votes
up vote
32
down vote
accepted
structs should not be looked on as "cheap objects"; they have similar feature sets that are overlapping in some areas and disjoint in others. For example:
- structs can't participate in polymorphism
- you can't treat a struct as an instance of an interface without boxing it (caveat: "constrained call", but that only works in some scenarios)
- a lot of library APIs won't work well (or possibly at all) with structs - they expect mutable POCOs; you'll probably want to use a library to get data from a database, serialize it, or render it in a UI - all of these things choke a bit with structs
- structs don't work well with some patterns, such as tree or sibling relationships (a
Foo
can't contain aFoo
if it is a struct) - there are others - structs, and immutable types generally, can be awkward to work with
Also, note that until very recently ("ref returns" and "ref locals") it was very hard to achieve some parts of "readonly structs allow you to only copy the reference"; this is now much simpler.
But frankly, in most scenarios POCOs are just easier to work with, and are fine for most application-code scenarios.
There are certainly times when structs are an amazing choice. It just isn't every time. I would however, support the notion that if you're going to use a struct
, it should be either a readonly struct
(by default) or a ref struct
(if you know why you're doing it); mutable non-ref
structs are a recipe for pain.
All valid points, thanks. Should I have to pick in a case where none of this apply would you suggest blindly going with readonly structs as default choice then? What are the cases you mentioned that make them hard to work with aside your points above?
â user4388177
Aug 14 at 9:16
@user4388177 the reality that data often needs to change during your code is a big one :) especially when the data is in something like aList<T>
, which doesn't have a "ref return" API (and cannot sensibly do so, because if somebody callsAdd
and forces the underlying array to grow, folks would expect theref
to point to the active array, but existingref
s would actually be interior pointers into the old array). This means you can't just domyRef = myRef.WithSomeChangedProperty("foo");
(to overwrite the value)
â Marc Gravellâ¦
Aug 14 at 9:21
That makes sense, but I'm more and more moving towards a more functional/immutable approach to the design. When I have to create a type and I want to enforce its immutability I believe readonly struct could be a good fit. Lists and arrays (even if you use immutable versions) most of the time fall into the "big objects" category so I wouldn't dream of using structs there.
â user4388177
Aug 14 at 9:30
@user4388177 for lists, though - if GC is your concern, you might want to consider pools see here for context (look for "Here's the gist" for the actual code uesd)
â Marc Gravellâ¦
Aug 14 at 9:34
Yep, we use ArrayPool, really useful. Thanks. I didn't know there was a GitHub issue about list pools, interesting. Although we ended up with some bugs in production with data from previous iterations XD.
â user4388177
Aug 14 at 9:36
 |Â
show 1 more comment
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
32
down vote
accepted
structs should not be looked on as "cheap objects"; they have similar feature sets that are overlapping in some areas and disjoint in others. For example:
- structs can't participate in polymorphism
- you can't treat a struct as an instance of an interface without boxing it (caveat: "constrained call", but that only works in some scenarios)
- a lot of library APIs won't work well (or possibly at all) with structs - they expect mutable POCOs; you'll probably want to use a library to get data from a database, serialize it, or render it in a UI - all of these things choke a bit with structs
- structs don't work well with some patterns, such as tree or sibling relationships (a
Foo
can't contain aFoo
if it is a struct) - there are others - structs, and immutable types generally, can be awkward to work with
Also, note that until very recently ("ref returns" and "ref locals") it was very hard to achieve some parts of "readonly structs allow you to only copy the reference"; this is now much simpler.
But frankly, in most scenarios POCOs are just easier to work with, and are fine for most application-code scenarios.
There are certainly times when structs are an amazing choice. It just isn't every time. I would however, support the notion that if you're going to use a struct
, it should be either a readonly struct
(by default) or a ref struct
(if you know why you're doing it); mutable non-ref
structs are a recipe for pain.
All valid points, thanks. Should I have to pick in a case where none of this apply would you suggest blindly going with readonly structs as default choice then? What are the cases you mentioned that make them hard to work with aside your points above?
â user4388177
Aug 14 at 9:16
@user4388177 the reality that data often needs to change during your code is a big one :) especially when the data is in something like aList<T>
, which doesn't have a "ref return" API (and cannot sensibly do so, because if somebody callsAdd
and forces the underlying array to grow, folks would expect theref
to point to the active array, but existingref
s would actually be interior pointers into the old array). This means you can't just domyRef = myRef.WithSomeChangedProperty("foo");
(to overwrite the value)
â Marc Gravellâ¦
Aug 14 at 9:21
That makes sense, but I'm more and more moving towards a more functional/immutable approach to the design. When I have to create a type and I want to enforce its immutability I believe readonly struct could be a good fit. Lists and arrays (even if you use immutable versions) most of the time fall into the "big objects" category so I wouldn't dream of using structs there.
â user4388177
Aug 14 at 9:30
@user4388177 for lists, though - if GC is your concern, you might want to consider pools see here for context (look for "Here's the gist" for the actual code uesd)
â Marc Gravellâ¦
Aug 14 at 9:34
Yep, we use ArrayPool, really useful. Thanks. I didn't know there was a GitHub issue about list pools, interesting. Although we ended up with some bugs in production with data from previous iterations XD.
â user4388177
Aug 14 at 9:36
 |Â
show 1 more comment
up vote
32
down vote
accepted
structs should not be looked on as "cheap objects"; they have similar feature sets that are overlapping in some areas and disjoint in others. For example:
- structs can't participate in polymorphism
- you can't treat a struct as an instance of an interface without boxing it (caveat: "constrained call", but that only works in some scenarios)
- a lot of library APIs won't work well (or possibly at all) with structs - they expect mutable POCOs; you'll probably want to use a library to get data from a database, serialize it, or render it in a UI - all of these things choke a bit with structs
- structs don't work well with some patterns, such as tree or sibling relationships (a
Foo
can't contain aFoo
if it is a struct) - there are others - structs, and immutable types generally, can be awkward to work with
Also, note that until very recently ("ref returns" and "ref locals") it was very hard to achieve some parts of "readonly structs allow you to only copy the reference"; this is now much simpler.
But frankly, in most scenarios POCOs are just easier to work with, and are fine for most application-code scenarios.
There are certainly times when structs are an amazing choice. It just isn't every time. I would however, support the notion that if you're going to use a struct
, it should be either a readonly struct
(by default) or a ref struct
(if you know why you're doing it); mutable non-ref
structs are a recipe for pain.
All valid points, thanks. Should I have to pick in a case where none of this apply would you suggest blindly going with readonly structs as default choice then? What are the cases you mentioned that make them hard to work with aside your points above?
â user4388177
Aug 14 at 9:16
@user4388177 the reality that data often needs to change during your code is a big one :) especially when the data is in something like aList<T>
, which doesn't have a "ref return" API (and cannot sensibly do so, because if somebody callsAdd
and forces the underlying array to grow, folks would expect theref
to point to the active array, but existingref
s would actually be interior pointers into the old array). This means you can't just domyRef = myRef.WithSomeChangedProperty("foo");
(to overwrite the value)
â Marc Gravellâ¦
Aug 14 at 9:21
That makes sense, but I'm more and more moving towards a more functional/immutable approach to the design. When I have to create a type and I want to enforce its immutability I believe readonly struct could be a good fit. Lists and arrays (even if you use immutable versions) most of the time fall into the "big objects" category so I wouldn't dream of using structs there.
â user4388177
Aug 14 at 9:30
@user4388177 for lists, though - if GC is your concern, you might want to consider pools see here for context (look for "Here's the gist" for the actual code uesd)
â Marc Gravellâ¦
Aug 14 at 9:34
Yep, we use ArrayPool, really useful. Thanks. I didn't know there was a GitHub issue about list pools, interesting. Although we ended up with some bugs in production with data from previous iterations XD.
â user4388177
Aug 14 at 9:36
 |Â
show 1 more comment
up vote
32
down vote
accepted
up vote
32
down vote
accepted
structs should not be looked on as "cheap objects"; they have similar feature sets that are overlapping in some areas and disjoint in others. For example:
- structs can't participate in polymorphism
- you can't treat a struct as an instance of an interface without boxing it (caveat: "constrained call", but that only works in some scenarios)
- a lot of library APIs won't work well (or possibly at all) with structs - they expect mutable POCOs; you'll probably want to use a library to get data from a database, serialize it, or render it in a UI - all of these things choke a bit with structs
- structs don't work well with some patterns, such as tree or sibling relationships (a
Foo
can't contain aFoo
if it is a struct) - there are others - structs, and immutable types generally, can be awkward to work with
Also, note that until very recently ("ref returns" and "ref locals") it was very hard to achieve some parts of "readonly structs allow you to only copy the reference"; this is now much simpler.
But frankly, in most scenarios POCOs are just easier to work with, and are fine for most application-code scenarios.
There are certainly times when structs are an amazing choice. It just isn't every time. I would however, support the notion that if you're going to use a struct
, it should be either a readonly struct
(by default) or a ref struct
(if you know why you're doing it); mutable non-ref
structs are a recipe for pain.
structs should not be looked on as "cheap objects"; they have similar feature sets that are overlapping in some areas and disjoint in others. For example:
- structs can't participate in polymorphism
- you can't treat a struct as an instance of an interface without boxing it (caveat: "constrained call", but that only works in some scenarios)
- a lot of library APIs won't work well (or possibly at all) with structs - they expect mutable POCOs; you'll probably want to use a library to get data from a database, serialize it, or render it in a UI - all of these things choke a bit with structs
- structs don't work well with some patterns, such as tree or sibling relationships (a
Foo
can't contain aFoo
if it is a struct) - there are others - structs, and immutable types generally, can be awkward to work with
Also, note that until very recently ("ref returns" and "ref locals") it was very hard to achieve some parts of "readonly structs allow you to only copy the reference"; this is now much simpler.
But frankly, in most scenarios POCOs are just easier to work with, and are fine for most application-code scenarios.
There are certainly times when structs are an amazing choice. It just isn't every time. I would however, support the notion that if you're going to use a struct
, it should be either a readonly struct
(by default) or a ref struct
(if you know why you're doing it); mutable non-ref
structs are a recipe for pain.
edited Aug 14 at 11:51
Adrian
7,72711436
7,72711436
answered Aug 14 at 8:15
Marc Gravellâ¦
756k18820862509
756k18820862509
All valid points, thanks. Should I have to pick in a case where none of this apply would you suggest blindly going with readonly structs as default choice then? What are the cases you mentioned that make them hard to work with aside your points above?
â user4388177
Aug 14 at 9:16
@user4388177 the reality that data often needs to change during your code is a big one :) especially when the data is in something like aList<T>
, which doesn't have a "ref return" API (and cannot sensibly do so, because if somebody callsAdd
and forces the underlying array to grow, folks would expect theref
to point to the active array, but existingref
s would actually be interior pointers into the old array). This means you can't just domyRef = myRef.WithSomeChangedProperty("foo");
(to overwrite the value)
â Marc Gravellâ¦
Aug 14 at 9:21
That makes sense, but I'm more and more moving towards a more functional/immutable approach to the design. When I have to create a type and I want to enforce its immutability I believe readonly struct could be a good fit. Lists and arrays (even if you use immutable versions) most of the time fall into the "big objects" category so I wouldn't dream of using structs there.
â user4388177
Aug 14 at 9:30
@user4388177 for lists, though - if GC is your concern, you might want to consider pools see here for context (look for "Here's the gist" for the actual code uesd)
â Marc Gravellâ¦
Aug 14 at 9:34
Yep, we use ArrayPool, really useful. Thanks. I didn't know there was a GitHub issue about list pools, interesting. Although we ended up with some bugs in production with data from previous iterations XD.
â user4388177
Aug 14 at 9:36
 |Â
show 1 more comment
All valid points, thanks. Should I have to pick in a case where none of this apply would you suggest blindly going with readonly structs as default choice then? What are the cases you mentioned that make them hard to work with aside your points above?
â user4388177
Aug 14 at 9:16
@user4388177 the reality that data often needs to change during your code is a big one :) especially when the data is in something like aList<T>
, which doesn't have a "ref return" API (and cannot sensibly do so, because if somebody callsAdd
and forces the underlying array to grow, folks would expect theref
to point to the active array, but existingref
s would actually be interior pointers into the old array). This means you can't just domyRef = myRef.WithSomeChangedProperty("foo");
(to overwrite the value)
â Marc Gravellâ¦
Aug 14 at 9:21
That makes sense, but I'm more and more moving towards a more functional/immutable approach to the design. When I have to create a type and I want to enforce its immutability I believe readonly struct could be a good fit. Lists and arrays (even if you use immutable versions) most of the time fall into the "big objects" category so I wouldn't dream of using structs there.
â user4388177
Aug 14 at 9:30
@user4388177 for lists, though - if GC is your concern, you might want to consider pools see here for context (look for "Here's the gist" for the actual code uesd)
â Marc Gravellâ¦
Aug 14 at 9:34
Yep, we use ArrayPool, really useful. Thanks. I didn't know there was a GitHub issue about list pools, interesting. Although we ended up with some bugs in production with data from previous iterations XD.
â user4388177
Aug 14 at 9:36
All valid points, thanks. Should I have to pick in a case where none of this apply would you suggest blindly going with readonly structs as default choice then? What are the cases you mentioned that make them hard to work with aside your points above?
â user4388177
Aug 14 at 9:16
All valid points, thanks. Should I have to pick in a case where none of this apply would you suggest blindly going with readonly structs as default choice then? What are the cases you mentioned that make them hard to work with aside your points above?
â user4388177
Aug 14 at 9:16
@user4388177 the reality that data often needs to change during your code is a big one :) especially when the data is in something like a
List<T>
, which doesn't have a "ref return" API (and cannot sensibly do so, because if somebody calls Add
and forces the underlying array to grow, folks would expect the ref
to point to the active array, but existing ref
s would actually be interior pointers into the old array). This means you can't just do myRef = myRef.WithSomeChangedProperty("foo");
(to overwrite the value)â Marc Gravellâ¦
Aug 14 at 9:21
@user4388177 the reality that data often needs to change during your code is a big one :) especially when the data is in something like a
List<T>
, which doesn't have a "ref return" API (and cannot sensibly do so, because if somebody calls Add
and forces the underlying array to grow, folks would expect the ref
to point to the active array, but existing ref
s would actually be interior pointers into the old array). This means you can't just do myRef = myRef.WithSomeChangedProperty("foo");
(to overwrite the value)â Marc Gravellâ¦
Aug 14 at 9:21
That makes sense, but I'm more and more moving towards a more functional/immutable approach to the design. When I have to create a type and I want to enforce its immutability I believe readonly struct could be a good fit. Lists and arrays (even if you use immutable versions) most of the time fall into the "big objects" category so I wouldn't dream of using structs there.
â user4388177
Aug 14 at 9:30
That makes sense, but I'm more and more moving towards a more functional/immutable approach to the design. When I have to create a type and I want to enforce its immutability I believe readonly struct could be a good fit. Lists and arrays (even if you use immutable versions) most of the time fall into the "big objects" category so I wouldn't dream of using structs there.
â user4388177
Aug 14 at 9:30
@user4388177 for lists, though - if GC is your concern, you might want to consider pools see here for context (look for "Here's the gist" for the actual code uesd)
â Marc Gravellâ¦
Aug 14 at 9:34
@user4388177 for lists, though - if GC is your concern, you might want to consider pools see here for context (look for "Here's the gist" for the actual code uesd)
â Marc Gravellâ¦
Aug 14 at 9:34
Yep, we use ArrayPool, really useful. Thanks. I didn't know there was a GitHub issue about list pools, interesting. Although we ended up with some bugs in production with data from previous iterations XD.
â user4388177
Aug 14 at 9:36
Yep, we use ArrayPool, really useful. Thanks. I didn't know there was a GitHub issue about list pools, interesting. Although we ended up with some bugs in production with data from previous iterations XD.
â user4388177
Aug 14 at 9:36
 |Â
show 1 more comment
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f51836634%2freadonly-struct-vs-classes%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
1
Because you want to access and edit the same object by reference?
â MineR
Aug 14 at 8:09
10
Pick
struct
orclass
, first and foremost based on whether you want value semantics or reference semantics.â Damien_The_Unbeliever
Aug 14 at 8:11
Also no inheritance.
â MineR
Aug 14 at 8:14
3
You've taken limited range of use-cases, and tried to ask generic question, based on them. Reference types are not data containers only.
â Dennis
Aug 14 at 8:20
1
@jrh - yep, they will behave that way in a lot of ways. I can still pick 'em apart with good old
ReferenceEquals
though :-)â Damien_The_Unbeliever
Aug 14 at 12:27