Skip to content

Commit

Permalink
Merge pull request #776 from unoplatform/dev/dr/keDoc
Browse files Browse the repository at this point in the history
docs(keyEquality): Add docuemntation about the key equality concept
  • Loading branch information
dr1rrb authored Sep 29, 2022
2 parents c0fc2c1 + cae14a5 commit 8ceff60
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 0 deletions.
112 changes: 112 additions & 0 deletions doc/Overview/KeyEquality/concept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
uid: Overview.KeyEquality.Concept
---
# Concept

When working with immutable objects (usually `record`), each modification on an entity means that a new instance is created.
While this offers lots of (great advantages)[https://en.wikipedia.org/wiki/Immutable_object] it makes more difficult to properly track (and animate)
changes on an (immutable) item in a (immutable) collection.

The _key equality_ is a standarized way to determine that 2 objects that are not `Equals` are however representing the same entity.

For instance:
```csharp

public partial record Person(string Name, int Age);

var john1 = new Person("John Doe", 20);
var john2 = john1 with { Age=21 };

Console.WriteLine("Are the same : " + john1.Equals(john2));
Console.WriteLine("Are the same person : " + john1.KeyEquals(john2));
```

This would output:
```
Are the same : false
Are the same person : true
```

In this example the _key_ is the `Name`, and any instance of `Person` that `Name == "John Doe"` is condered to represent the same person,
no matter values of the other properties.

Then if you are dealing with list:
```csharp
var list1 = ImmutableList.Create(john1);
var list2 = list1.Replace(john1, john2);
```

When comparing the `list1` and the `list2` the `IKeyEquatable<T>` allows you to detect that the item is only a newer version of the same entity,
so visually we only need to update the current item and not animate the removal and then the add of the item.

# IKeyEquatable<T>

This is like `IEquatable<T>` but specialized for the _key_ comparison.

When you implement this, you should compare only the keys of your object.

> [!TIP]
> You usually don't have to implement it by yourself, cf. (generation)[#Generation].
# KeyEqualityComparer

This is like the `EqualityComparer` but which relies on the `IKeyEquatable` instead of `IEquatable` to check equality.

> [!TIP]
> As all types are not necessarily implementing `IKeyEquatable<T>`, there is no equivalent to the `EqualityComparer<T>.Default`.
> You can however use the static `KeyEqualityComparer.Find<T>()` to dynamically get a _key equality_ comparer,
> if the given `T` does implement `IKeyEquatable<T>`.
# Generation

The `IKeyEquatable<T>` implementation is automatically generated for `partial record` that has a property named `Id` or `Key`.

## How to configure keys?
You have several way to configure the keys:

1. Add the `[Key]` attribute on properties that shoudl be used for _key equality_.
```csharp
public partial record MyItem(
[property:Key] Guid EntityId,
[property:Key] string SourceId,
string Value);
```

> [!IMPORTANT]
> As soon as a property has been flagged with the `[Key]` attribute, the implicit keys are not used.
> [!NOTE]
> This is the single way to have more than one property to compute _key equality_.
> [!NOTE]
> You can use indifferently the `Uno.Extensions.Equality.KeyAttribute` or the `System.ComponentModel.DataAnnotations.KeyAttribute`
2. Add the `[ImplicitKeyEquality]` attribute on your record
```csharp
[ImplicitKeyEquality("EntityId")]
public partial record MyItem(Guid EntityId, string SourceId, string Value);
```

3. Change the default keys for the whole project by setting the `[ImplicitKeyEquality]` on the assembly:
```csharp
[assembly:ImplicitKeyEquality("Id", "Key", "EntityId")]
```

> [!IMPORTANT]
> The generation of `IKeyEquatable<T>` using implicit keys will use only **one** matching property.
> The properties a tested in the order in which they have been defined on the `[ImplicitKeyEquality]` attribute.
> This means that in teh example above, if a record have 2 properties `Id` and `EntityId`, only the property named `Id` will be used.
## How to disable generation?

You can disable the generation on a given type by adding `[ImplicitKeyEquality(IsEnabled = false)]` on it.

To disable the generation for the whole project you set that attibute directly on the assembly:
```csharp
[assembly:ImplicitKeyEquality(IsEnabled = false)]
```

> ![IMPORTANT]
> This disable only the generation based on implicit keys.
> If you have a record that has a property flagged with the `[Key]` attribute,
> the generator will still generate the `IKeyEquatable<T>` implementation for that type.
49 changes: 49 additions & 0 deletions doc/Overview/KeyEquality/rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
uid: Overview.Reactive.Rules
---
# Feeds code analyzers

## KE0001
**A record eligible to IKeyEquatable generation must be partial.**

You have a record that has a property that can be used to generate `IKeyEquatable` implemenation for that record,
but it was not delacred as `partial`.

Add the `partial` modifier on your record, or disable the `IKeyEquatable` implemenation generated using `[ImplicitKeyEquality(IsEnabled = false)]`
on the record itself or on the whole assembly (not recommended).

## KE0002
**A record that implements GetKeyHashCode should also implement KeyEquals.**

You have method named `GetKeyHashCode` in your record, but there is no `KeyEquals`.

If you want to define a custom way to compute _key equality_, you have to implement both the `GetKeyHashCode` and `KeyEquals`.

## KE0003
**A record that implements KeyEquals should also implement GetKeyHashCode.**

You have method named `KeyEquals` in your record, but there is no `GetKeyHashCode`.

If you want to define a custom way to compute _key equality_, you have to implement both the `GetKeyHashCode` and `KeyEquals`.

## KE0004
**A record flagged with `[ImplicitKeyEquality]` attribute must have an eligible key property**

You have a record that is flagged with `[ImplicitKeyEquality]` attribute, but there is no property that match any of the defined implicit keys.

You should either remove the `[ImplicitKeyEquality]` attribute, either add a valid property name.

## KE0005
**A record should have only one matching key property for implicit IKeyEquatable generation.**

You have a record that is eligible for implicit key equality generation, but it has more than one matching implicit key.
The generated implementation of `IKeyEquatable` will use only the first key.

You should either explicitly flag all needed key properties with the `[Key]` attribute,
either remove/rename properties that should not be used as key.

> [!NOTE]
> By default the key equlity generation will serach for properties named `Id` or `Key`.
> You can customize it using the `ImplicitKeyEquality` attribute.
> For instance setting `[assembly:ImplicitKeyEquality("Id", "MyCustomKey")]` on your assembly will no longer search for `Key` properties,
> but will instead serach for `MyCustomKey` properties.
4 changes: 4 additions & 0 deletions doc/Overview/Reactive/listfeed.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ The `IListFeed<T>` is _feed_ specialized for handling collections.
It allows the declaration of an operator directly on items instead of dealing with the list itself.
A _list feed_ goes in `None` if the list does not have any elements.

> [!NOTE]
> `ListFeed<T>` is using the _key equality_ to track multiple version of a same entity whitin different messages of the `ListFeed<T>`.
> (Read more about _key equality_.)[../KeyEquality/concept.md]
## Sources: How to create a list feed
To create an `IListFeed<T>`, on the `ListFeed` class, the same `Async`, `AsyncEnumerable` and `Create` methods found on `Feed` can be used.

Expand Down

0 comments on commit 8ceff60

Please sign in to comment.