Aspnetcore: Blazor: EditContext fieldstate tracking falls apart when model implements GetHashCode

Created on 31 Dec 2019  路  18Comments  路  Source: dotnet/aspnetcore

Describe the bug

When a model implements GetHashCode, the _fieldState dictionary within EditContext continually adds to it's collection for the same field, effectively breaking the field-state logic.

To Reproduce

In my case, I used a protocol buffer to generate a simple C# class. Here's a gist: https://gist.github.com/aaronhudon-ts/6f9b079014115f539feef9b599a2c14d

  • Put a breakpoint in EditContext.cs GetFieldState. Start changing values in fields. You will notice that the call to TryGetValue always fails due to the underlying GetHashCode call of the FieldIdentifier.
area-blazor bug

All 18 comments

Thanks for the issue report @aaronhudon-ts. We'll look in to how to resolve this soon.

Thanks for the issue report @aaronhudon-ts. We'll look in to how to resolve this soon.

Thanks @pranavkm If you can suggest any workaround for the time being that does not require me to abandon protobufs as the data model, it would be most appreciated. I'm targeting a production release by the end of the month.

Does using a derived type as a model that overrides GetHashCode work? Looking at EditContext FieldIdentifier, nothing immediately jumps at me that would work as a suitable work around.

@pranavkm Ok, assuming I can override GetHashCode in the protobuf generated code (I think it's possible with partials), what would the fix be?

```C#
public override int GetHashCode() => RuntimeHelpers.GetHashCode(this);

public override bool Equals(object other) => ReferenceEquals(this, other);
```

public override int GetHashCode() => RuntimeHelpers.GetHashCode(this);

public override bool Equals(object other) => ReferenceEquals(this, other);

Thanks! I'll give it a try. Would be awesome to summon @jskeet for feedback on any potential side effects of doing this in the C# proto runtime

Not sure where the proto runtime comes in here - protobuf messages are explicitly equal by value.

The generated C# for the protobuf implements GetHashCode (e.g. like below). What are the implications of changing the generated code to be like @pranavkm suggests
E.g.

` ``
public override int GetHashCode() {
int hash = 1;
if (String1.Length != 0) hash ^= String1.GetHashCode();
if (String2.Length != 0) hash ^= String2.GetHashCode();
if (Number1 != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Number1);
if (Number2 != 0D) hash ^= pbc::ProtobufEqualityComparers.BitwiseDoubleEqualityComparer.GetHashCode(Number2);
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
```

Will need to look at the whole thread again when I'm on a laptop.

@aaronhudon-ts the plan is to fix this specific issue in 3.1 patch release. With a fix in place, you shouldn't need changes to protobuf's code gen, which is absolutely fine as it is.

@pranavkm - what's the root cause here? The object overrides GetHashCode and then.... ?

If the model produces hashcodes based on it's property values, the lookups become unstable. The hypothesis is that we need reference equality to compare model instances.

GetHashCode should not be calculated based on state unless it is immutable.

This isn't a Blazor issue, its fundamental to .net dictionaries.

You can override GetHashCode() for immutable reference types. In general, for mutable reference types, you should override GetHashCode() only if:

You can compute the hash code from fields that are not mutable; or

You can ensure that the hash code of a mutable object does not change while the object is contained in a collection that relies on its hash code.

This is definitely not going to change in protobuf. It would be massively breaking - and a bad idea, IMO. Yes, you shouldn't mutate protos which have already been stored as keys in dictionaries - but in every other situation it's useful for protos to be easily comparable by current value (and it's useful to be able to use them as dictionary keys and not mutate them).

This is definitely _not_ going to change in protobuf. It would be massively breaking - and a bad idea, IMO. Yes, you shouldn't mutate protos which have already been stored as keys in dictionaries - but in _every other situation_ it's useful for protos to be easily comparable by current value (and it's useful to be able to use them as dictionary keys and _not_ mutate them).

The answer is to create the Dictionary passing in a ReferenceEqualityComparer as its IEqualityComparer.

So I suppose it kind of is a Blazor issue.

This is definitely _not_ going to change in protobuf. It would be massively breaking - and a bad idea, IMO. Yes, you shouldn't mutate protos which have already been stored as keys in dictionaries - but in _every other situation_ it's useful for protos to be easily comparable by current value (and it's useful to be able to use them as dictionary keys and _not_ mutate them).

In my case, the protobuf object is being used as the "bound" data model in the Blazor Edit context, and the UI data entry is causing the protobuf's properties to change.

@aaronhudon-ts There is a solution -> https://github.com/dotnet/aspnetcore/issues/18069#issuecomment-572218294

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aurokk picture aurokk  路  3Comments

guardrex picture guardrex  路  3Comments

BrennanConroy picture BrennanConroy  路  3Comments

FourLeafClover picture FourLeafClover  路  3Comments

rbanks54 picture rbanks54  路  3Comments