Add [StructuralEquality] to your classes to avoid writing Equals and GetHashCode methods.
PostSharp synthesizes a member-by-member structural equality implementation of Equals and GetHashCode of annotated classes and adds those implementations directly to your assembly so they don't clutter your code.
June 5, 2020. We've covered this add-in in a blog post.
This is an add-in for PostSharp. It modifies your assembly during compilation by using IL weaving.
Your code:
[StructuralEquality]classDog{publicstringName{get;set;}publicintAge{get;set;}publicstaticbooloperator==(Dogleft,Dogright)=>Operator.Weave(left,right);publicstaticbooloperator!=(Dogleft,Dogright)=>Operator.Weave(left,right);}What gets compiled:
classDog:IEquatable<Dog>{publicstringName{get;set;}publicintAge{get;set;}publicstaticbooloperator==(Dogleft,Dogright)=>object.Equals(left,right);publicstaticbooloperator!=(Dogleft,Dogright)=>!object.Equals(left,right);publicoverrideboolEquals(Dogother){boolresult=false;if(!object.ReferenceEquals(null,other)){if(object.ReferenceEquals(this,other)){result=true;}elseif(object.Equals(Name,other.Name)&&Age==other.Age){result=true;}}returnresult;}publicoverrideboolEquals(objectother){boolresult=false;if(!object.ReferenceEquals(null,other)){if(object.ReferenceEquals(this,other)){result=true;}elseif(this.GetType()==other.GetType()){result=Equals((Dog)other);}}returnresult;}publicoverrideintGetHashCode(){intnum=Name?.GetHashCode()??0;return(num*397)^Age;}}- Install the NuGet package:
PM> Install-Package PostSharp.Community.StructuralEquality - Get a free PostSharp Community license at https://www.postsharp.net/essentials
- When you compile for the first time, you'll be asked to enter the license key.
Add
[StructuralEquality]to the classes where you want it to apply.Choose, for each class, whether you want to also implement
==and!=operators.If yes, add the following code to the class:
publicstaticbooloperator==(YourClassNameleft,YourClassNameright)=>Operator.Weave(left,right);publicstaticbooloperator!=(YourClassNameleft,YourClassNameright)=>Operator.Weave(left,right);
If no, use
[StructuralEquality(DoNotAddEqualityOperators=true)]instead.
- The add-in creates an override for
EqualsandGetHashCode, it implementsIEquatable<ClassName>and creates a strongly-typedEqualsmethod to implement theIEquatableinterface.- If any of those methods already exist, they're used and the add-in doesn't overwrite them.
- The
EqualsandGetHashCodemethods take into account all fields in the class, including backing fields of auto-implemented properties, except for fields and properties marked with[IgnoreDuringEquals]. They don't take into account fields in the base class, but they do call the base class'sEqualsandGetHashCode, if it exists, unless you useIgnoreBaseClass. - You can combine this add-in's automatic logic with your custom logic by annotating methods in the class with
[AdditionalEqualsMethod]and[AdditionalGetHashCodeMethod]. - The default settings, if you choose no options, are very close to the default implementation created by ReSharper's Generate equality members.
- For fields that implement
IEnumerable, inEquals, the equality is for each element (it's deep equality). The same is true forGetHashCode. - Adding the
==and!=operators will result in compiler warnings CS0660 and CS0661, which tell you to add custom Equals and GetHashCode implementations. This add-in is doing this for you, but only after the compiler runs. To suppress these false-positives, you can either do so:- per project, by adding
<PropertyGroup><NoWarn>CS0660;CS0661</NoWarn></PropertyGroup>to the project file - per source file, by adding
#pragma warning disable CS0660, CS0661
- per project, by adding
Your code:
[StructuralEquality(TypeCheck=TypeCheck.ExactlyTheSameTypeAsThis)]publicclassAdvancedCase:AdvancedBaseClass{protectedstringfield;publicList<List<object>>lists{get;}=newList<List<object>>();[IgnoreDuringEquals]publicfloatDoNotUse{get;set;}[AdditionalEqualsMethod]publicboolAndFloatWithinRange(AdvancedCaseother){returnMath.Abs(this.DoNotUse-other.DoNotUse)<0.1f;}publicstaticbooloperator==(AdvancedCaseleft,AdvancedCaseright)=>Operator.Weave(left,right);publicstaticbooloperator!=(AdvancedCaseleft,AdvancedCaseright)=>Operator.Weave(left,right);}[StructuralEquality(DoNotAddEqualityOperators=true,DoNotAddGetHashCode=true)]publicclassAdvancedBaseClass{privateintbaseField;}What gets compiled, and why:
publicclassAdvancedCase:AdvancedBaseClass,IEquatable<AdvancedCase>{protectedstringfield;publicList<List<object>>lists{get;}=newList<List<object>>();publicfloatDoNotUse{get;set;}[AdditionalEqualsMethod]publicboolAndFloatWithinRange(AdvancedCaseother)=>Math.Abs(DoNotUse-other.DoNotUse)<0.1f;// Operators are transformed into actual code:publicstaticbooloperator==(AdvancedCaseleft,AdvancedCaseright)=>object.Equals(left,right);publicstaticbooloperator!=(AdvancedCaseleft,AdvancedCaseright)=>!object.Equals(left,right);publicoverrideboolEquals(AdvancedCaseother){boolresult=false;if(!object.ReferenceEquals(null,other)){if(object.ReferenceEquals(this,other)){result=true;}elseif(base.Equals((object)other)&&// base.Equals is calledobject.Equals(field,other.field)&&// the "DoNotUse" field is not compared, it was excludedCollectionHelper.Equals(lists,other.lists)&&// collections are compared element-by-element AndFloatWithinRange(other))// the custom logic is appended at the end of the Equals method{result=true;}}returnresult;}publicoverrideboolEquals(objectother){boolresult=false;if(!object.ReferenceEquals(null,other)){if(object.ReferenceEquals(this,other)){result=true;}elseif((object)GetType()==other.GetType()){result=Equals((AdvancedCase)other);}}returnresult;}publicoverrideintGetHashCode(){intnum=field?.GetHashCode()??0;if(lists!=null){// hash code is element-by-element for collections IEnumeratorenumeratorVariable=((IEnumerable)lists).GetEnumerator();while(enumeratorVariable.MoveNext()){num=((num*397)^(enumeratorVariable.Current?.GetHashCode()??0));}}returnnum;}}publicclassAdvancedBaseClass:IEquatable<AdvancedBaseClass>{// No GetHashCode is created because of DoNotAddGetHashCode// The operators == and != still mean referential equality because of// DoNotAddEqualityOperators.privateintbaseField;publicoverrideboolEquals(AdvancedBaseClassother){boolresult=false;if(!object.ReferenceEquals(null,other)){if(object.ReferenceEquals(this,other)){result=true;}elseif(baseField==other.baseField){result=true;}}returnresult;}publicoverrideboolEquals(objectother){boolresult=false;if(!object.ReferenceEquals(null,other)){if(object.ReferenceEquals(this,other)){result=true;}elseif((object)GetType()==other.GetType()){result=Equals((AdvancedBaseClass)other);}}returnresult;}}Published under the MIT license.
- Copyright © PostSharp Technologies, Rafał Jasica, Simon Cropp, and contributors
- Icon by Pixel Buddha, https://www.flaticon.com