C# list of custom class .Contains() - c#

i have a list of custom class and before adding to the list i want to check if the list has the same instance (not one attribute - all of them)
public class Function
{
public string Name;
public string RT;
public int ParamCount;
public List<string> ParamDT;
public Function()
{
ParamDT = new List<string>();
}
}
i tried overriding Equals() and GetHashCode()
but it didn't work
Equals()
public override bool Equals(object obj)
{
var item = obj as Function;
if (item == null)
{
return false;
}
return this.Name == item.Name && this.RT == item.RT &&
this.ParamCount == item.ParamCount && this.ParamDT.Equals(item.ParamDT);
}
GetHashCode()
public override int GetHashCode()
{
return this.Name.GetHashCode();
}

ParamDT is also a list, you have to check its items also individually to compare properly.
this.ParamDT.Equals(item.ParamDT);
Having said that, list is not the structure you should be using if you want single instances of your object. There is a lot of overhead trying to search for equality in list as you will be searching the entire list. Try to use a set/dictionary based structure.
Your implementation of GetHasCode function is also not proper. It is based only on Name property while in equality you are using all the properties, this will lead to undesirable characteristics. Please read the MSDN documentation for a better implementation.

A simple Equals and GetHashCode is below:
protected bool Equals(Function other)
{
return string.Equals(Name, other.Name) && string.Equals(RT, other.RT) && ParamCount == other.ParamCount && Equals(ParamDT, other.ParamDT);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Function) obj);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (Name != null ? Name.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (RT != null ? RT.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ ParamCount;
hashCode = (hashCode * 397) ^ (ParamDT != null ? ParamDT.GetHashCode() : 0);
return hashCode;
}
}
you can then use a HashSet which is a collection that contains no duplicate elements. This will ensure there is no code written for checking uniqueness in List. Link is here: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1?redirectedfrom=MSDN&view=netframework-4.7.2

Related

Why isn't my incremental source generator generating incrementally?

I've taken a stab at writing an incremental source generator; it is generating the correct source code, but it's not doing so incrementally. I feel like it has to be something wrong with my Initialize method or my custom return type (ClassInfo) not being cache friendly. I've never written an IEquatable either, so I really thing it has something to do with that.
ClassInfo
public readonly struct ClassInfo : IEquatable<ClassInfo>
{
public readonly string? Namespace { get; }
public readonly string Name { get; }
public readonly ImmutableArray<IPropertySymbol> PropertyNames { get; }
public ClassInfo(ITypeSymbol type)
{
Namespace = type.ContainingNamespace.IsGlobalNamespace ? null : type.ContainingNamespace.ToDisplayString();
Name = type.Name;
PropertyNames = GetPropertyNames(type);
}
private static ImmutableArray<IPropertySymbol> GetPropertyNames(ITypeSymbol type)
{
return type.GetMembers()
.Select(m =>
{
// Only properties
if (m is not IPropertySymbol prop || m.DeclaredAccessibility != Accessibility.Public)
return null;
// Without ignore attribute
if (GenHelper.IsPropsToStringIgnore(m))
return null;
return (IPropertySymbol)m;
//return SymbolEqualityComparer.Default.Equals(prop.Type, type) ? prop.Name : null;
})
.Where(m => m != null)!
.ToImmutableArray<IPropertySymbol>();
}
public override bool Equals(object? obj) => obj is ClassInfo other && Equals(other);
public bool Equals(ClassInfo other)
{
if (ReferenceEquals(null, other))
return false;
//if (ReferenceEquals(this, other))
// return true;
return Namespace == other.Namespace
&& Name == other.Name
&& PropertyNames.SequenceEqual(other.PropertyNames); // <-- Problem Line
}
public override int GetHashCode()
{
var hashCode = (Namespace != null ? Namespace.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ Name.GetHashCode();
hashCode = (hashCode * 397) ^ PropertyNames.GetHashCode(); // <-- Problem Line
return hashCode;
}
}
IncrementalGenerator.Initialize
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddSource("PropsToStringAttribute.g.cs", SourceText.From(AttributeTexts.PropsToStringAttribute, Encoding.UTF8));
ctx.AddSource("PropToStringAttribute.g.cs", SourceText.From(AttributeTexts.PropToStringAttribute, Encoding.UTF8));
ctx.AddSource("PropsToStringIgnoreAttribute.g.cs", SourceText.From(AttributeTexts.PropsToStringIgnoreAttribute, Encoding.UTF8));
});
var classProvider = context.SyntaxProvider
.CreateSyntaxProvider(
static (node, _) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 },
static (ctx, ct) => GetClassInfoOrNull(ctx, ct)
)
.Where(type => type is not null)
.Collect()
.SelectMany((classes, _) => classes.Distinct());
context.RegisterSourceOutput(classProvider, Generate);
}
GetClassInfoOrNull
internal static ClassInfo? GetClassInfoOrNull(GeneratorSyntaxContext context, CancellationToken cancellationToken)
{
// We know the node is a ClassDeclarationSyntax thanks to IsSyntaxTargetForGeneration
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
var type = ModelExtensions.GetDeclaredSymbol(context.SemanticModel, classDeclarationSyntax, cancellationToken) as ITypeSymbol;
return IsPropsToString(type) ? new ClassInfo(type!) : null;
}
IsPropsToString
public static bool IsPropsToString(ISymbol? type)
{
return type is not null &&
type.GetAttributes()
.Any(a => a.AttributeClass is
{
Name: ClassAttributeName,
ContainingNamespace:
{
Name: PTSNamespace,
ContainingNamespace.IsGlobalNamespace: true
}
});
}
IsPropsToStringIgnore
public static bool IsPropsToStringIgnore(ISymbol type)
{
return type is not null &&
type.GetAttributes()
.Any(a => a.AttributeClass is
{
Name: PropertyIgnoreAttributeName,
ContainingNamespace:
{
Name: PTSNamespace,
ContainingNamespace.IsGlobalNamespace: true
}
});
}
As a side note, I mostly followed this https://www.thinktecture.com/en/net/roslyn-source-generators-performance/
Edit 9/2/22
I have narrowed down the problem to two lines of code noted above in ClassInfo.Equals and ClassInfo.GetHashCode; the two lines that deal with equating the array of names. I commented out those two lines and started to get incremental code generation. However, I wasn't getting new code generation when properties changes (as espected), I instead had to change the name of the class(es) to get new code generated (again, as expected).
Edit 9/7/22
Added project to GitHub
Edit 9/8/22
I tried not using SequenceEquals to compare my PropertyNames array, but it didnt work.
public bool Equals(ClassInfo other)
{
if (PropertyNames.Count() != other.PropertyNames.Count())
return false;
int i = 0;
bool propIsEqual = true;
while (propIsEqual && i < PropertyNames.Count())
{
propIsEqual &= SymbolEqualityComparer.Default.Equals(PropertyNames[i], other.PropertyNames[i]);
i++;
}
return Namespace == other.Namespace
&& Name == other.Name
&& propIsEqual;
//PropertyNames.SequenceEqual(other.PropertyNames); // <-- Problem Line
}
You declared ClassInfo as struct, so it makes no sense to use ReferenceEquals in your Equals implementation. It will box the struct and always return other references.
public bool Equals(ClassInfo other)
{
return Namespace == other.Namespace
&& Name == other.Name
&& PropertyNames.SequenceEqual(other.PropertyNames);
}
PropertyNames.GetHashCode() only gets the hash code of the array object but does not include the array items; however it should, as Equals does it by calling SequenceEqual. The explicit IStructuralEquatable implementation of GetHashCode in ImmutableArray<T> does what we need.
public override int GetHashCode()
{
unchecked {
int hashCode = Namespace != null ? Namespace.GetHashCode() : 0;
hashCode = (hashCode * 397) ^ Name.GetHashCode();
hashCode = (hashCode * 397) ^
((IStructuralEquatable)PropertyNames)
.GetHashCode(EqualityComparer<IPropertySymbol>.Default);
return hashCode;
}
}
The IPropertySymbol implementation(s) must override the Equals and the GetHashCode methods as well. This is also important for SequenceEqual to work as expected.
Edit 22/09/08
We can remove uncertainties about mysterious equality comparers by comparing the properties ourselves:
public bool Equals(ClassInfo other)
{
// Length is faster than the extension method Count()
if (PropertyNames.Length != other.PropertyNames.Length) {
return false;
}
for (int i = 0; i < PropertyNames.Length; i++) {
if (PropertyNames[i].Name != other.PropertyNames[i].Name) {
return false; // We don't need to do the other tests.
}
}
return Namespace == other.Namespace && Name == other.Name;
}
Note: In your test project the "Counter = x" now gets incremented when we change a property name, but not if we add e.g. a comment.
Consequently, we do the same for GetHashCode (see also What is the best algorithm for overriding GetHashCode? and especially Jon Skeet's answer):
public override int GetHashCode()
{
unchecked {
int hash = 17;
hash = hash * 23 + (Namespace ?? "").GetHashCode();
hash = hash * 23 + Name.GetHashCode();
foreach (IPropertySymbol property in PropertyNames) {
hash = hash * 23 + property.Name.GetHashCode();
}
return hash;
}
}

List equality of a custom class

I have a class A, which holds a string property and overwrites Equals for equality testing.
public class A
{
public string Prop { get; }
public A(string val)
{
Prop = val;
}
public override bool Equals(object obj)
{
return obj is A arg && (Prop == arg.Prop);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
I also have a class B which has a List<A> as property:
public class B
{
public IReadOnlyList<A> Prop { get; }
public B(IReadOnlyList<A> val)
{
Prop = val;
}
public override bool Equals(object obj)
{
// ...
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
I wanna be able to compare to instances of B for equality and order.
How can I write the Equals method in B by not rewriting the same code I wrote in A?
Is there a way to reuse the A Equals?
Update: My first version assumed B is derived from A.
A.Equals:
If A is not sealed, obj is A ... can return a false positive if different types are compared. So the corrected version:
public override bool Equals(object obj)
{
return obj is A other
&& this.Prop == other.Prop
&& this.GetType() == other.GetType(); // not needed if A is sealed
}
A.GetHashCode:
base.GetHashCode will return different hash codes for different but equal instances, which is wrong. Derive the hashcode from self properties instead. If Prop acts like some ID, then simply return Prop.GetHashCode()
B.Equals:
public override bool Equals(object obj)
{
return obj is B other
&& this.Prop.SequenceEqual(other.Prop) // will re-use A.Equals
&& this.Prop.GetType() == other.Prop.GetType() // not needed if different IReadOnlyList types are ok
&& this.GetType() == other.GetType(); // not needed if B is sealed
}
B.GetHashCode:
You can aggregate the hash codes of A instances. Here I use a simple XOR but if the same items can often come in a different order you can come up with something more fancy.
return Prop.Aggregate(0, (h, i) => h ^ i.GetHashCode());
Implementing Equals for a list can be done by using the SequenceEquals method (from System.Linq namespace), which ensures that each item in one list equals the item at the same index in the other list.
One thing you might consider changing, however is your implementation of GetHashCode. This method should return the same number if two items are equal (though it's not guaranteed that two items with the same hash code are equal). Using base.GetHashCode() does not meet this requirement, since the base is object in this case; according to the documentation, "hash codes for reference types are computed by calling the Object.GetHashCode method of the base class, which computes a hash code based on an object's reference", so objects only return the same HashCode if they refer to the exact same object.
The HashCode should be based on the same properties used to determine equality, so in this case we want to use Prop.GetHashCode() for class A, and we want to aggregate the hashcode for all the items in Prop for class B.
Here's one way the classes could be refactored:
public class A : IEquatable<A>
{
public string Prop { get; }
public A(string val)
{
Prop = val;
}
public bool Equals(A other)
{
if (other == null) return false;
return Prop == other.Prop;
}
public override bool Equals(object obj)
{
return Equals(obj as A);
}
public override int GetHashCode()
{
return Prop.GetHashCode();
}
}
public class B : IEquatable<B>
{
public IReadOnlyList<A> Prop { get; }
public B(IReadOnlyList<A> val)
{
Prop = val;
}
public bool Equals(B other)
{
if (other == null) return false;
if (ReferenceEquals(this, other)) return true;
if (Prop == null) return other.Prop == null;
return other.Prop != null && Prop.SequenceEqual(other.Prop);
}
public override bool Equals(object obj)
{
return Equals(obj as B);
}
public override int GetHashCode()
{
return Prop?.Aggregate(17,
(current, item) => current * 17 + item?.GetHashCode() ?? 0)
?? 0;
}
}
Linq contains a useful method to compare collections: SequenceEqual
public override bool Equals(object obj)
{
if (!(obj is B other))
{
return false;
}
if (this.Prop == null || other.Prop == null)
{
return false;
}
return this.Prop.SequenceEqual(other.Prop);
}
Also, implement IEquatable<T> when you override Equals.
How about something like this:
public override bool Equals(object obj)
{
if(!(obj is B))
{
return false;
}
var b = obj as B;
if(b.Prop.Count != this.Prop.Count)
{
return false;
}
for(var i =0; i < Prop.Count; i++)
{
if (!Prop.ElementAt(i).Equals(b.Prop.ElementAt(i)))
{
return false;
}
}
return true;
}

Warning: Object defines operator == or operator != but does not override Object.Equals(object o)

I'm programming in C# Unity and have really annoying problem - I want to define special Pair class with following relations:
public class Pair<T1>{
public int First;
public T1 Second;
public bool Equals(Pair<T1> b){
return First == b.First;
}
public static bool operator==(Pair<T1> a, Pair<T1> b){
return a.First == b.First;
}
public static bool operator!=(Pair<T1> a, Pair<T1> b){
return a.First != b.First;
}
}
Which gives me following warning:
Warning CS0660: 'Pair' defines operator == or operator != but does
not override Object.Equals(object o) (CS0660) (Assembly-CSharp)
But also when I spawn two objects of Pair type with same First integer, their == operator returns True (as I want). When I only declare Equals function, same == operator returns False value (I understand that somehow Unity compares their addressees in memory), with no warnings. Is there any method to avoid warnings and still get True value of == operator?
Just override that method to make the compiler happy :
public override bool Equals(object o)
{
if(o == null)
return false;
var second = o as Pair<T1>;
return second != null && First == second.First;
}
public override int GetHashCode()
{
return First;
}
The method you created is a custom equals method, you need to override that of the object class (which is used in the == && != operators)
You need to override the Equal:
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Pair<T1>) obj);
}
And the GetHashCode method:
public override int GetHashCode()
{
unchecked
{
return (First*397) ^ EqualityComparer<T1>.Default.GetHashCode(Second);
}
}

C# class definition HELP

This is a project question that i just cant seem to answer
using System;
namespace ConsoleApplication2
{
internal class Equipment : IComparable
{
private readonly string type;
private readonly int serialNo;
private string colour;
public decimal cost;
public Equipment(string type, int serialNo)
{
this.type = type == null ? "" : type.Trim();
this.serialNo = serialNo;
}
public string Key
{
get { return type + ":" + serialNo; }
}
int IComparable.CompareTo(object obj)
{
return 0;
}
}
}
(a) Override the appropriate method o ensure that different instances of the class that represent the same equipment item will be considered the same in the system.
(b) Override the appropriate method to enable instances of this class to be stored (and found) by key in a hash table
You should override the Equals and GetHashCode methods for this purpose.
Override Equals() with an appropriate logic of comparision
Override GetHashCode(), see GetHashCode Guidelines in C#
You must start reading this before doing such a task
Why is it important to override GetHashCode when Equals method is overriden in C#?
Writing GetHashCode manually is not that easy. Anyhow, that's code generated for this purpose by ReSharper. It's a complete solution. (It should be contained within your class definition of course). But what would you say, if you were asked - why and how it works? It might be embarassing.
So, apart from GetHashCode and Equals, which others have suggested you reading about, you might also look up http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx as well as http://msdn.microsoft.com/en-us/library/a569z7k8(v=VS.100).aspx
As for the mystery behind 397 in GetHashCode, have a look at this question here on StackOverflow: Why is '397' used for ReSharper GetHashCode override?
public bool Equals(Equipment other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return Equals(other.colour, colour) && other.cost == cost && other.serialNo == serialNo && Equals(other.type, type);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != typeof (Equipment))
{
return false;
}
return Equals((Equipment) obj);
}
// note: if "Override the appropriate method to enable instances of this class
// to be stored (and found) by key in a hash table" is supposed to mean that only type and
// serialNo should be taken into account (since they are used to generate
// the Key value) - just remove the lines with cost and colour
public override int GetHashCode()
{
unchecked
{
int result = (colour != null ? colour.GetHashCode() : 0);
result = (result*397) ^ cost.GetHashCode();
result = (result*397) ^ serialNo;
result = (result*397) ^ (type != null ? type.GetHashCode() : 0);
return result;
}
}

GetHashCode Equality

I've wondered about this, so I figure I'll ask it.
Most places you'll see use the same semantic logic for overriding Equals as GetHashCode for memberwise equality...however they usually use different implementations:
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
var other = (MyType)obj;
if (other.Prop1 != Prop1)
{
return false;
}
return true;
}
public override int GetHashCode()
{
int hash = -657803396;
num ^= Prop1.GetHashCode();
return num;
}
If you're implementing memberwise equality for your type (lets say for storing in a dictionary), why not just override GetHashCode then do something like this for Equals:
public override bool Equals(object obj)
{
return this.HashEqualsAndIsSameType(obj);
}
public static bool HashEquals(this object source, object obj)
{
if (source != null && obj != null)
{
return source.GetHashCode() == obj.GetHashCode();
}
if (source != null || obj != null)
{
return false;
}
return true;
}
public static bool HashEqualsAndIsSameType<T>(this T source, object obj)
{
return (obj == null || obj.GetType() == typeof(T)) && source.HashEquals(obj);
}
Because there is a real risk of conflicts. Hash-codes are not unique. They can (when different) prove inequality, but never equality. When looking for an item:
get the hash-code(s)
if the hash-code is different, the object is different; discard it
if the hash-code is the same, check Equals:
if Equals reports true they are the same
else discard
Consider long... since hash-code is int, it is easy to see that there are lots and lots of conflicts.
Hashes are not 1-to-1, you can have multiple different values that hash to the same value, but which should compare as not equal. So you cannot really implement Equals in terms of GetHashCode. This is why you have collisions in a hash table, and why a hash table lookup must involve call(s) to both GetHashCode and Equals.

Categories