Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small tweaks to structs concept docs #2346

Merged
merged 4 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions concepts/structs/about.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# About

C# `struct`s are closely related `class`s. They have state and behavior. They can have the same kinds of members: constructors, methods, fields, properties, etc.
C# `struct`s are closely related to `class`es.
They have state and behavior.
They can have the same kinds of members: constructors, methods, fields, properties, etc.

Fields and properties can be simple types, `struct`s or reference types. `struct`s observe the same rules about scope, read/write rules and access levels as do `class`s.
Fields and properties can be simple types, `struct`s or reference types.
`struct`s observe the same rules about scope, read/write rules and access levels as do `class`es.

```csharp
enum Unit
Expand Down Expand Up @@ -32,9 +35,11 @@ new Weight(77.5, Unit.Kg).ToString();
// => "77.6Kg"
```

One of the main things to remember is that when one struct is assigned to a variable or passed as a parameter the values are copied across so changes to the original variable will not affect the copied one and vice versa. In summary, `struct`s are **value types**.
One of the main things to remember is that when one struct is assigned to a variable or passed as a parameter the values are copied across so changes to the original variable will not affect the copied one and vice versa.
In summary, `struct`s are **value types**.

This [article][class-or-struct] discusses the differences between `struct`s and `class`s. You will see from the article that `struct`s tend to be lightweight and [immutable][structs-immutable] although this guidance is not enforced (by default) by the compiler or runtime.
This [article][class-or-struct] discusses the differences between `struct`s and `class`es.
You will see from the article that `struct`s tend to be lightweight and [immutable][structs-immutable] although this guidance is not enforced (by default) by the compiler or runtime.

There are a couple of things that you will come up against (and about which the compiler will remind you):

Expand All @@ -46,21 +51,32 @@ As a result of points 1 and 3 above there is no way for the developer of a `stru

## Common structs

You will see from the documentation that there is a close relationship between primitives and structs. See [`Int32/int`][int32], for an example. A more conventional example of a`struct`is the type [`TimeSpan`][time-span].
You will see from the documentation that there is a close relationship between primitives and structs.
See [`Int32/int`][int32], for an example.
A more conventional example of a`struct`is the type [`TimeSpan`][time-span].

Instances of `TimeSpan` behave much like numbers with comparison operators like `>` and `<` and arithmetic operators. You can implement these operators for your own `struct`s when you need them.
Instances of `TimeSpan` behave much like numbers with comparison operators like `>` and `<` and arithmetic operators.
You can implement these operators for your own `struct`s when you need them.

One thing to note about `TimeSpan` is that it implements a number of interfaces e.g. `IComparable<TimeSpan>`. Although `struct`s cannot be derived from other `struct`s they can implement interfaces.
One thing to note about `TimeSpan` is that it implements a number of interfaces e.g. `IComparable<TimeSpan>`.
Although `struct`s cannot be derived from other `struct`s they can implement interfaces.

## Equality

Equality testing for `struct`s can often be much simpler than that for `class`s as it simply compares fields for equality by default. There is no need to override `object.Equals()` (or `GetHashCode()`). Remember that if you are relying on `Object.GetHashCode()` you must still ensure that the fields involved in generating the hash code (i.e. all the fields) must not change while a hashed collection is use. Effectively, this means that structs used in this way should be immutable.
Equality testing for `struct`s can often be much simpler than that for `class`es as it simply compares fields for equality by default.
There is no need to override `object.Equals()` (or `GetHashCode()`).
Remember that if you are relying on `Object.GetHashCode()` you must still ensure that the fields involved in generating the hash code (i.e. all the fields) must not change while a hashed collection is use.
Effectively, this means that structs used in this way should be immutable.

In contrast to the method, `Equals()`, there is no default implementation of the equality operators, `==` and `!=`. If your `struct` needs them then you will have to implement them.
In contrast to the method, `Equals()`, there is no default implementation of the equality operators, `==` and `!=`.
If your `struct` needs them then you will have to implement them.

On the other hand, this [article][equality] describes how performance can be optimised by creating your own custom `Equals()` and `GetHashCode()` method as is often done with `class`s. The difference in the case of this exercise was about 20% in a not very rigorous comparison but that may be on the low side because all the fields are of the same type - see below.
On the other hand, this [article][equality] describes how performance can be optimised by creating your own custom `Equals()` and `GetHashCode()` method as is often done with `class`es.
The difference in the case of this exercise was about 20% in a not very rigorous comparison but that may be on the low side because all the fields are of the same type - see below.

There are discussions on the [web][equality-performance] about speed improvements, where the `Equals()` method is not overridden, if all fields are of the same type. The difference in this exercise of including disparate fields was about 60%. This is not mentioned in Microsoft's documentation so that makes it an un-documented implementation detail, and it should be exploited judiciously.
There are discussions on the [web][equality-performance] about speed improvements, where the `Equals()` method is not overridden, if all fields are of the same type.
The difference in this exercise of including disparate fields was about 60%.
This is not mentioned in Microsoft's documentation so that makes it an un-documented implementation detail, and it should be exploited judiciously.

```csharp
public bool Equals(Weight other)
Expand Down
4 changes: 3 additions & 1 deletion concepts/structs/introduction.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Introduction

C# `struct`s are closely related `class`s. They have state and behavior. They have constructors that take arguments, instances can be assigned, tested for equality and stored in collections.
C# `struct`s are closely related to `class`es.
They have state and behavior.
They have constructors that take arguments, instances can be assigned, tested for equality and stored in collections.

```csharp
enum Unit
Expand Down
Loading