principlecsharpCritical
When to use record vs class vs struct
Viewed 0 times
structclassuserecordwhen
Problem
-
Should I be using
-
Should I be using
What is a Record? Anthony Giretti Introducing C# 9: Records
should
Should I be using
Record for all of my DTO classes that move data between controller and service layer?-
Should I be using
Record for all my request bindings since ideally I would want the request sent to the controller to be immutable for my asp.net APIWhat is a Record? Anthony Giretti Introducing C# 9: Records
public class HomeController : Controller
{
public async Task Search(SearchParameters searchParams)
{
await _service.SearchAsync(searchParams);
}
}should
SearchParameters be made a Record?Solution
Short version
Can your data type be a value type? Go with
Use
For further practical examples of
Long version
A
Structures are value types. Classes are reference types. Records are by default immutable reference types.
When you need some sort of hierarchy to describe your data types like inheritance or a
Records solve the problem when you want your type to be a value oriented by default. Records are reference types but with the value oriented semantic.
With that being said, ask yourself these questions...
Does your data type respect all of these rules:
Does your data type encapsulate some sort of a complex value? Is the value immutable? Do you use it in unidirectional (one way) flow?
BTW: Don't forget about anonymous objects. There will be an anonymous records in C# 10.0.
Notes
A record instance can be mutable if you make it mutable.
An assignment of a record is a shallow copy of the record. A copy by
See this example (using top-level feature in C# 9.0):
`using System.Collections.Generic;
using static System.Console;
var foo = new SomeRecord(new List());
var fooAsShallowCopy = foo;
var fooAsWithCopy = foo with { }; // A syntactic sugar for new SomeRecord(foo.List);
var fooWithDifferentList = foo with { List = new List() { "a", "b" } };
var differentFooWithSameList = new SomeRecord(foo.List); // This is the same like foo with { };
foo.List.Add("a");
WriteLine($"Count in foo: {foo.List.Count}"); // 1
WriteLine($"Count in fooAsShallowCopy: {fooAsShallowCopy.List.Count}"); // 1
WriteLine($"Count in fooWithDifferentList: {fooWithDifferentList.List.Count}"); // 2
WriteLine($"Count in differentFooWithSameList: {differentFooWithSameList.List.Count}"); // 1
WriteLine($"Count in fooAsWithCopy: {fooAsWithCopy.List.Count}"); // 1
WriteLine("");
WriteLine($"Equals (foo & fooAsShallowCopy): {Equals(foo, fooAsShallowCopy)}"); // True. The lists inside are the same.
WriteLine($"Equals (foo & fooWithDifferentList): {Equals(foo, fooWithDifferentList)}"); // False. The lists are different
WriteLine($"Equals (foo & differentFooWithSameList): {Equals(foo, differentFooWithSameList)}"); // True. The list are the same.
WriteLine($"Equals (foo & fooAsWithCopy): {Equals(foo, fooAsWithCopy)}"); // True. The list are the same, see below.
WriteLine($"ReferenceEquals (foo.List & fooAsShallowCopy.List): {ReferenceEquals(foo.List, fooAsShallowCopy.List)}"); // True. The records property points to the same reference.
WriteLine($"ReferenceEquals (foo.List & fooWithDifferentList.List): {ReferenceEquals(foo.List, fooWithDifferentList.List)}"); // False. The list are different instances.
WriteLine($"ReferenceEquals (foo.List & differentFooWithSameList.List): {ReferenceEquals(foo.List, differentFooWithSameList.List)}"); // True. The records property points to the same reference.
WriteLine($"ReferenceEquals (foo.List & fooAsWithCopy.List): {ReferenceEquals(foo.List, fooAsWithCopy.List)}"); // True. The records property points to the same reference.
WriteLine("");
WriteLine($"ReferenceEquals (foo & fooAsShallowCopy): {ReferenceEquals(foo, fooAsShallowCopy)}"); // True. !!! fooAsCopy is pure shallow copy of foo. !!!
WriteLine($"ReferenceEquals (foo & fooWithDifferentList): {ReferenceEquals(foo, fooWithDifferentList)}"); // False. These records are two different reference variables.
WriteLine($"ReferenceEquals (foo & differentFooWithSameList): {
Can your data type be a value type? Go with
struct. No? Does your type describe a value-like, preferably immutable state? Go with record.Use
class otherwise. So...- Yes, use
records for your DTOs if it is one way flow.
- Yes, immutable request bindings are an ideal user case for a
record
- Yes,
SearchParametersare an ideal user case for arecord.
For further practical examples of
record use, you can check this repo.Long version
A
struct, a class and a record are user data types.Structures are value types. Classes are reference types. Records are by default immutable reference types.
When you need some sort of hierarchy to describe your data types like inheritance or a
struct pointing to another struct or basically things pointing to other things, you need a reference type.Records solve the problem when you want your type to be a value oriented by default. Records are reference types but with the value oriented semantic.
With that being said, ask yourself these questions...
Does your data type respect all of these rules:
- It logically represents a single value, similar to primitive types (int, double, etc.).
- It has an instance size under 16 bytes.
- It is immutable.
- It will not have to be boxed frequently.
- Yes? It should be a
struct.
- No? It should be some reference type.
Does your data type encapsulate some sort of a complex value? Is the value immutable? Do you use it in unidirectional (one way) flow?
- Yes? Go with
record.
- No? Go with
class.
BTW: Don't forget about anonymous objects. There will be an anonymous records in C# 10.0.
Notes
A record instance can be mutable if you make it mutable.
class Program
{
static void Main()
{
var test = new Foo("a");
Console.WriteLine(test.MutableProperty);
test.MutableProperty = 15;
Console.WriteLine(test.MutableProperty);
//test.Bar = "new string"; // will not compile
}
}
record Foo(string Bar)
{
internal double MutableProperty { get; set; } = 10.0;
}
An assignment of a record is a shallow copy of the record. A copy by
with expression of a record is neither a shallow nor a deep copy. The copy is created by a special clone method emitted by C# compiler. Value-type members are copied and boxed. Reference-type members are pointed to the same reference. You can do a deep copy of a record if and only if the record has value type properties only. Any reference type member property of a record is copied as a shallow copy.See this example (using top-level feature in C# 9.0):
`using System.Collections.Generic;
using static System.Console;
var foo = new SomeRecord(new List());
var fooAsShallowCopy = foo;
var fooAsWithCopy = foo with { }; // A syntactic sugar for new SomeRecord(foo.List);
var fooWithDifferentList = foo with { List = new List() { "a", "b" } };
var differentFooWithSameList = new SomeRecord(foo.List); // This is the same like foo with { };
foo.List.Add("a");
WriteLine($"Count in foo: {foo.List.Count}"); // 1
WriteLine($"Count in fooAsShallowCopy: {fooAsShallowCopy.List.Count}"); // 1
WriteLine($"Count in fooWithDifferentList: {fooWithDifferentList.List.Count}"); // 2
WriteLine($"Count in differentFooWithSameList: {differentFooWithSameList.List.Count}"); // 1
WriteLine($"Count in fooAsWithCopy: {fooAsWithCopy.List.Count}"); // 1
WriteLine("");
WriteLine($"Equals (foo & fooAsShallowCopy): {Equals(foo, fooAsShallowCopy)}"); // True. The lists inside are the same.
WriteLine($"Equals (foo & fooWithDifferentList): {Equals(foo, fooWithDifferentList)}"); // False. The lists are different
WriteLine($"Equals (foo & differentFooWithSameList): {Equals(foo, differentFooWithSameList)}"); // True. The list are the same.
WriteLine($"Equals (foo & fooAsWithCopy): {Equals(foo, fooAsWithCopy)}"); // True. The list are the same, see below.
WriteLine($"ReferenceEquals (foo.List & fooAsShallowCopy.List): {ReferenceEquals(foo.List, fooAsShallowCopy.List)}"); // True. The records property points to the same reference.
WriteLine($"ReferenceEquals (foo.List & fooWithDifferentList.List): {ReferenceEquals(foo.List, fooWithDifferentList.List)}"); // False. The list are different instances.
WriteLine($"ReferenceEquals (foo.List & differentFooWithSameList.List): {ReferenceEquals(foo.List, differentFooWithSameList.List)}"); // True. The records property points to the same reference.
WriteLine($"ReferenceEquals (foo.List & fooAsWithCopy.List): {ReferenceEquals(foo.List, fooAsWithCopy.List)}"); // True. The records property points to the same reference.
WriteLine("");
WriteLine($"ReferenceEquals (foo & fooAsShallowCopy): {ReferenceEquals(foo, fooAsShallowCopy)}"); // True. !!! fooAsCopy is pure shallow copy of foo. !!!
WriteLine($"ReferenceEquals (foo & fooWithDifferentList): {ReferenceEquals(foo, fooWithDifferentList)}"); // False. These records are two different reference variables.
WriteLine($"ReferenceEquals (foo & differentFooWithSameList): {
Context
Stack Overflow Q#64816714, score: 590
Revisions (0)
No revisions yet.