Why isn't my incremental source generator generating incrementally? - c#

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;
}
}

Related

C# list of custom class .Contains()

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

Compare 2 class objects and ignore 1 (or more) property?

So I created a class like this:
public class ClassName
{
public int ID;
public String n_1 {get; set; }
public String n_2 {get; set; }
// ....
public String n_x {get; set; }
}
Later in my code I compare 2 ClassName objects:
ClassName Item_1 /*...*/ ;
ClassName Item_2 /*...*/ ;
Like this:
if (Item_1 != Item_2 && Item_1.n_a == Item_2.n_a)
{
//do something
}
Now my Problem is that Item_1.ID and Item_2.ID should be ignored. Is there a easy way to do this? The only solution I came up with is a no brainer of like
if ( (Item_1.n_1 != Item_2.n_1 || Item_1.n_2 != Item_2.n_2 || /* ... */ ) && Item_1.n_a == Item_2.n_a)
Override the object.Equals method in your class and specify how you want it to be compared.
Then use !Item1.Equals(Item2) instead of !=
https://learn.microsoft.com/en-us/dotnet/api/system.object.equals?view=netframework-4.7.2
The easy way is what you are actually doing , just create a method like this
public bool Method(ClassName Item_1,ClassName Item2)
{
bool check=false;
if ( (Item_1.n_1 != Item_2.n_1 || Item_1.n_2 != Item_2.n_2 || /* ... */ ) && Item_1.n_a == Item_2.n_a)
check=true
return check
}
To perform something like Item_1 != Item_2 or Item_1 == Item_2 and get all properties (or the ones you want) to be compared, you need to implement your own Equals method and ==, != operators overloads.
== operator, from MSDN:
For reference types other than string, == returns true if its two operands refer to the same object.
For that you can start with some straightforward solution, to implement IEquatable<T> interface, fill your comparing logic inside Equals method and overload == and != operators internally calling your type-safe Equals method:
public class ClassName : IEquatable<ClassName>
{
public int ID;
public String n_1 { get; set; }
public String n_2 { get; set; }
// ....
public String n_x { get; set; }
public static bool operator ==(ClassName obj1, ClassName obj2)
{
if (((object)obj1) == null || ((object)obj2) == null)
return Equals(obj1, obj2);
return obj1.Equals(obj2);
}
public static bool operator != (ClassName obj1, ClassName obj2)
{
if (((object)obj1) == null || ((object)obj2) == null)
return !Equals(obj1, obj2);
return !obj1.Equals(obj2);
}
public bool Equals(ClassName obj)
{
if (obj == null) return false;
return (n_1 == obj.n_1) && (n_2 == obj.n_2) && (n_x == obj.n_x); //you can ignore ID here
}
public override bool Equals(object obj)
{
if (obj == null)
return false;
ClassName classNameObj = obj as ClassName;
if (classNameObj == null)
return false;
else
return Equals(classNameObj);
}
public override int GetHashCode()
{
//This code was generated by VS ide, you can write your own hashing logic
var hashCode = 1032198799;
hashCode = hashCode * -1521134295 + ID.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(n_1);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(n_2);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(n_x);
return hashCode;
}
}
Then you can check in your if:
if (Item_1 != Item_2)
{
//Do Something
}
For Equals method you should stick to Guidelines for Overriding Equals() and Operator == .
References: == Operator, != Operator, IEquatable Interface, IEquatable.Equals(T) Method, Guidelines for Overriding Equals() and Operator ==

C#: How to evaluate multipe Comparer classes for an object when checking equality?

Motivation
I'm looking to implement IComparer<> in a similar way to the demo code below. Where Foo is the type of objects I need to compare. It does not implement IComparable, but I'm providing an IComparer class for each field so the user can elect to equate to instances based on one field value.
enum Day {Sat, Sun, Mon, Tue, Wed, Thu, Fri};
class Foo {
public int Bar;
public string Name;
public Day Day;
}
Comparer classes are:
// Compares all fields in Foo
public class FooComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo x, Foo y)
{
if (ReferenceEquals(x, y)) return true;
return x.Bar == y.Bar && x.Name == y.Name && return x.Day == y.Day;
}
public int GetHashCode(Foo obj)
{
unchecked
{
var hashCode = obj.Bar;
hashCode = (hashCode * 397) ^ (obj.Name != null ? obj.Name.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int) obj.Day; 0);
return hashCode;
}
}
}
// Compares only in Foo.Bar
public class FooBarComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo x, Foo y)
{
if (ReferenceEquals(x, y)) return true;
return x.Bar == y.Bar;
}
public int GetHashCode(Foo obj)
{
unchecked
{
var hashCode = obj.Bar;
hashCode = (hashCode * 397) ^ (obj.Name != null ? obj.Name.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int) obj.Day; 0);
return hashCode;
}
}
}
// Compares only in Foo.Name
public class FooNameComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo x, Foo y)
{
if (ReferenceEquals(x, y)) return true;
return x.Name == y.Name;
}
public int GetHashCode(Foo obj)
{
unchecked
{
var hashCode = obj.Bar;
hashCode = (hashCode * 397) ^ (obj.Name != null ? obj.Name.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int) obj.Day; 0);
return hashCode;
}
}
}
// Compares only in Foo.Day
public class FooDayComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo x, Foo y)
{
if (ReferenceEquals(x, y)) return true;
return x.Day == y.Day;
}
public int GetHashCode(Foo obj)
{
unchecked
{
var hashCode = obj.Bar;
hashCode = (hashCode * 397) ^ (obj.Name != null ? obj.Name.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int) obj.Day; 0);
return hashCode;
}
}
}
Question
I want to allow the user to be able to combine multiple Comparer types to evaluate two instances of Type Foo. I'm not sure how to do that.
Idea
What I came up with is something like this, where I AND the results of comparisons done by all comparers in the list:
bool CompareFoo(Foo a, Foo b, params IComparer[] comparers)
{
bool isEqual = true;
// Or the list and return;
foreach (var comparer in comparers)
{
isEqual = isEqual && comparer.Equals(x,y);
}
return isEqual;
}
Notes
My target .NET version is 4.5.
I may be stuck with C# 5.0.
Also, may be stuck with `MSBuild 12.0
This is my first time to use IComparer.
You can combine multiple IEqualityComparer<Foo> objects by defining an additional comparer that takes other comparers as constructor parameters:
public class CompositeFooComparer : IEqualityComparer<Foo>
{
private IEqualityComparer<Foo>[] comparers;
public CompositeFooComparer(params IEqualityComparer<Foo>[] comparers)
{
this.comparers = comparers;
}
public bool Equals(Foo x, Foo y)
{
foreach (var comparer in comparers)
{
if (!comparer.Equals(x, y))
{
return false;
}
}
return true;
}
public int GetHashCode(Foo obj)
{
var hash = 0;
foreach (var comparer in comparers)
{
hash = hash * 17 + (comparer.GetHashCode(obj));
}
return hash;
}
}
Then you can create and use it like this:
var fooA = new Foo
{
Bar = 5,
Day = Day.Fri,
Name = "a"
};
var fooB = new Foo
{
Bar = 5,
Day = Day.Fri,
Name = "b"
};
var barComparer = new FooBarComparer();
var dayComparer = new FooDayComparer();
var compositeComparer = new CompositeFooComparer(barComparer, dayComparer);
Console.WriteLine(compositeComparer.Equals(fooA, fooB)); // displays "true"
Another idea is to have a comparer that does know which fields will be compared, based on boolean parameters instead.
public class ConfigurableFooComparer : IEqualityComparer<Foo>
{
private readonly bool compareBar;
private readonly bool compareName;
private readonly bool compareDay;
public ConfigurableFooComparer(bool compareBar, bool compareName, bool compareDay)
{
this.compareBar = compareBar;
this.compareName = compareName;
this.compareDay = compareDay;
}
public bool Equals(Foo x, Foo y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x == null || y == null)
{
return false;
}
if (compareBar && x.Bar != y.Bar)
{
return false;
}
if (compareName && x.Name != y.Name)
{
return false;
}
if (compareDay && x.Day != y.Day)
{
return false;
}
return true;
}
public int GetHashCode(Foo obj)
{
unchecked
{
var hash = 0;
if (compareBar)
{
hash = hash * 17 + obj.Bar.GetHashCode();
}
if (compareName)
{
hash = hash * 17 + (obj.Name == null ? 0 : obj.Name.GetHashCode());
}
if (compareDay)
{
hash = hash * 17 + obj.Day.GetHashCode();
}
return hash;
}
}
And then using it like this:
var barAndDayComparer = new ConfigurableFooComparer(compareBar: true, compareName: false, compareDay: true);
Console.WriteLine(barAndDayComparer.Equals(fooA, fooB));
It seems to me that what you are trying to achieve feels very similar to a Chain-of-responsibility.
So why don't you arrange all your Foo comparers in a chain-like structure and make the chain extensible so that new links can be added at run-time?
Here's is the idea:
the client will implement whatever Foo comparers it wants and all of them will be neatly arranged in a way that all of them will be called one by one and if anyone returns false then the whole comparison returns false!
Here's the code:
public abstract class FooComparer
{
private readonly FooComparer _next;
public FooComparer(FooComparer next)
{
_next = next;
}
public bool CompareFoo(Foo a, Foo b)
{
return AreFoosEqual(a, b)
&& (_next?.CompareFoo(a, b) ?? true);
}
protected abstract bool AreFoosEqual(Foo a, Foo b);
}
public class FooNameComparer : FooComparer
{
public FooNameComparer(FooComparer next) : base(next)
{
}
protected override bool AreFoosEqual(Foo a, Foo b)
{
return a.Name == b.Name;
}
}
public class FooBarComparer : FooComparer
{
public FooBarComparer(FooComparer next) : base(next)
{
}
protected override bool AreFoosEqual(Foo a, Foo b)
{
return a.Bar == b.Bar;
}
}
The idea of the FooComparer abstract class is having something like a chain manager; it handles the calling of the whole chain and forces its derived classes to implement the code to compare the Foo's, all while exposing the method CompareFoo which is what the client will use.
And how will the client use it? well it can do something like this:
var manager = new FooManager();
manager.FooComparer
= new FooNameComparer(new FooBarComparer(null));
manager.FooComparer.CompareFoo(fooA, fooB);
But it's cooler if they can register the FooComparer chain to the IoC Container!
Edit
This is a more simplistic approach I've been using for a while to custom compare things:
public class GenericComparer<T> : IEqualityComparer<T> where T : class
{
private readonly Func<T, object> _identitySelector;
public GenericComparer(Func<T, object> identitySelector)
{
_identitySelector = identitySelector;
}
public bool Equals(T x, T y)
{
var first = _identitySelector.Invoke(x);
var second = _identitySelector.Invoke(y);
return first != null && first.Equals(second);
}
public int GetHashCode(T obj)
{
return _identitySelector.Invoke(obj).GetHashCode();
}
}
public bool CompareFoo2(Foo a, Foo b, params IEqualityComparer<Foo>[] comparers)
{
foreach (var comparer in comparers)
{
if (!comparer.Equals(a, b))
{
return false;
}
}
return true;
}
And let the client do:
var areFoosEqual = CompareFoo2(a, b,
new GenericComparer<Foo>(foo => foo.Name),
new GenericComparer<Foo>(foo => foo.Bar))
It may be possible to adapt the GenericComparer to have multiple identity selector as to pass them all in a single lambda but we would also need to update its GetHashCode method to compute the HashCode correctly using all the identity objects.
You can achieve by something like this :
class Program
{
static bool CompareFoo(Foo a, Foo b, List<IEqualityComparer<Foo>> comparers)
{
return comparers.All(com => com.Equals(a, b));
}
static void Main(string[] args)
{
List<IEqualityComparer<Foo>> compares = new List<IEqualityComparer<Foo>>
{
new FooNameComparer(),
new FooBarComparer()
};
var test1 = CompareFoo(new Foo { Name = "aio", Bar = 10 }, new Foo { Name = "aio", Bar = 10 }, compares);
var test2 = CompareFoo(new Foo { Name = "Foo1", Bar = 10 }, new Foo { Name = "Foo2", Bar = 10 }, compares);
}
}
Note : you must consider all possible conditions in your compare classes, for example in "FooNameComparer" class the below code can become a bug :
return x.Name == y.Name;
because if the "Name" property of two classes pass null, null == null return true! the code should be :
public bool Equals(Foo x, Foo y)
{
if (ReferenceEquals(x, y)) return true;
if (string.IsNullOrEmpty(x?.Name) || string.IsNullOrEmpty(y?.Name))
return false;
return x.Name == y.Name;
}

c# Stabilise complicated sort [duplicate]

I want an alphabetic sort with one exception.
There is a Group with a Name = "Public" and an ID = "0" that I want first.
(would rather use ID = 0)
After that then sort the rest by Name.
This does not return public first.
public IEnumerable<GroupAuthority> GroupAuthoritysSorted
{
get
{
return GroupAuthoritys.OrderBy(x => x.Group.Name);
}
}
What I want is:
return GroupAuthoritys.Where(x => x.ID == 0)
UNION
GroupAuthoritys.Where(x => x.ID > 0).OrderBy(x => x.Group.Name);
GroupAuthority has a public property Group and Group has Public properties ID and Name.
I used basically the accepted answer
using System.ComponentModel;
namespace SortCustom
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TestSort();
}
private void TestSort()
{
List<CustomSort> LCS = new List<CustomSort>();
LCS.Add(new CustomSort(5, "sss"));
LCS.Add(new CustomSort(6, "xxx"));
LCS.Add(new CustomSort(4, "xxx"));
LCS.Add(new CustomSort(3, "aaa"));
LCS.Add(new CustomSort(7, "bbb"));
LCS.Add(new CustomSort(0, "pub"));
LCS.Add(new CustomSort(2, "eee"));
LCS.Add(new CustomSort(3, "www"));
foreach (CustomSort cs in LCS) System.Diagnostics.Debug.WriteLine(cs.Name);
LCS.Sort();
foreach (CustomSort cs in LCS) System.Diagnostics.Debug.WriteLine(cs.Name);
}
}
public class CustomSort : Object, INotifyPropertyChanged, IComparable<CustomSort>
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, e);
}
private Int16 id;
private string name;
public Int16 ID { get { return id; } }
public String Name { get { return name; } }
public int CompareTo(CustomSort obj)
{
if (this.ID == 0) return -1;
if (obj == null) return 1;
if (obj is CustomSort)
{
CustomSort comp = (CustomSort)obj;
if (comp.ID == 0) return 1;
return string.Compare(this.Name, comp.Name, true);
}
else
{
return 1;
}
}
public override bool Equals(Object obj)
{
// Check for null values and compare run-time types.
if (obj == null) return false;
if (!(obj is CustomSort)) return false;
CustomSort comp = (CustomSort)obj;
return (comp.ID == this.ID);
}
public override int GetHashCode()
{
return (Int32)ID;
}
public CustomSort(Int16 ID, String Name)
{
id = ID;
name = Name;
}
}
}
You need to use a comparison function, they are functions that from two instances of your type return an integer that return 0 if both are equals, a negative value if the first is less than the second and a positive value if the first is greater than the second.
MSDN has a nice table that is easier to follow than text (StackOverflow still doesn't support tables in 2014)
IComparer<T>
Most sort methods accept a custom comparer implementation of type IComparer<T> you should create one encapsulating your custom rules for Group :
class GroupComparer : IComparer<Group>
{
public int Compare(Group a, Group b)
{
if (a != null && b != null && (a.Id == 0 || b.Id == 0))
{
if (a.Id == b.Id)
{
// Mandatory as some sort algorithms require Compare(a, b) and Compare(b, a) to be consistent
return 0;
}
return a.Id == 0 ? -1 : 1;
}
if (a == null || b == null)
{
if (ReferenceEquals(a, b))
{
return 0;
}
return a == null ? -1 : 1;
}
return Comparer<string>.Default.Compare(a.Name, b.Name);
}
}
Usage:
items.OrderBy(_ => _, new GroupAuthorityComparer());
IComparable<T>
If it is the only way to compare Group instances you should make it implement IComparable<T> so that no aditional code is needed if anyone want to sort your class :
class Group : IComparable<Group>
{
...
public int CompareTo(Group b)
{
if (b != null && (Id == 0 || b.Id == 0))
{
if (Id == b.Id)
{
// Mandatory as some sort algorithms require Compare(a, b) and Compare(b, a) to be consistent
return 0;
}
return Id == 0 ? -1 : 1;
}
return Comparer<string>.Default.Compare(Name, b.Name);
}
}
Usage:
items.OrderBy(_ => _.Group);
The choice between one way or the other should be done depending on where this specific comparer is used: Is it the main ordering for this type of item or just the ordering that should be used in one specific case, for example only in some administrative view.
You can even go one level up and provide an IComparable<GroupAuthority> implementation (It's easy once Group implement IComparable<Group>):
class GroupAuthority : IComparable<GroupAuthority>
{
...
public int CompareTo(GroupAuthority b)
{
return Comparer<Group>.Default.Compare(Group, b.Group);
}
}
Usage:
items.OrderBy(_ => _);
The advantage of the last one is that it will be used automatically, so code like: GroupAuthoritys.ToList().Sort() will do the correct thing out of the box.
You can try something like this
list.Sort((x, y) =>
{
if (x.Id == 0)
{
return -1;
}
if (y.Id == 0)
{
return 1;
}
return x.Group.Name.CompareTo(y.Group.Name);
});
Where list is List<T>.
This method takes advantage of custom sort option provided by List<T> using Comparison<T> delegate.
Basically what this method does is, it just adds special condition for comparison when Id, If it is zero it will return a value indicating the object is smaller which makes the object to come in top of the list. If not, it sorts the object using its Group.Name property in ascending order.
public IEnumerable<GroupAuthority> GroupAuthoritysSorted
{
get
{
return GroupAuthoritys.OrderBy(x => x.Group.ID == 0)
.ThenBy(x => x.Group.Name);
}
}

Error while fetching data from sorted dictionary in C#

I have a SortedDictionary<Package, List<string>>. Following is the Package class:
using System;
namespace GetPackageInfo
{
public class Package : IComparable, IEquatable<Package>
{
public string PackageName;
public string Version;
public Package()
{
}
public Package(string packageName)
{
this.PackageName = packageName;
}
public override int GetHashCode()
{
unchecked
{
int result = 17;
result = result * 23 + ((PackageName != null) ? this.PackageName.GetHashCode() : 0);
result = result * 23 + ((Version != null) ? this.Version.GetHashCode() : 0);
return result;
}
}
public bool Equals(Package other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return Equals(this.PackageName, other.PackageName) &&
Equals(this.Version, other.Version);
}
public override bool Equals(object obj)
{
Package temp = obj as Package;
if (temp == null)
return false;
return this.Equals(temp);
}
public override string ToString()
{
return string.Format("PackageName: {0}, Version: {1}", PackageName, Version);
}
public int CompareTo(object obj)
{
if (obj == null)
return 1;
if (obj != null)
return (Equals(obj) ? -1 : 1);
else
throw new ArgumentException("Object is not a Temperature");
}
Whenever I do a Contains or ContainsKey on the SortedDictionary, it does not work even when the name and version is the same.
if (nugetPackagesInfo.Keys.Any(p => p.Equals(package)))
{
//List<string> currPackage;
//nugetPackagesInfo.TryGetValue(package, out currPackage);
if (!nugetPackagesInfo[package].Contains(packageFile))
{
nugetPackagesInfo[package].Add(packageFile);
}
nuGetPackagesInfo is my dictionary object. The Any returns true though. But once it is passed and gets to nugetPackagesInfo[package], then it throws the KeyNotFoundException. Can you please help me figure it out? Is my CompareTo not correct?
Your implementation of CompareTo doesn't seem to be correct. In fact, you don't implement any ordering of packages. You should, most likely, order packages by name, and if equal by version.
The core of Package.CompareTo should look like this (simplified; not taking care of other == null):
// try name ordering
int nameOrdering = this.Name.CompareTo(other.Name);
// names not equal ⇒ ordering is clear and no need to inspect further
if (nameOrdering != 0)
{
return nameOrdering;
}
// names are equal ⇒ resort to version ordering
else
{
return this.Version.CompareTo(other.Version);
}
You should also read the documentation for String.CompareTo() because of its culture-specific semantics.
Change CompareTo and GetHashCode to these implementations.
public int CompareTo(object obj)
{
if (obj == null)
return 1;
return this.ToString().CompareTo(obj.ToString());
}
public override int GetHashCode()
{
unchecked
{
return ((PackageName != null ? PackageName.GetHashCode() : 0)*397) ^ (Version != null ? Version.GetHashCode() : 0);
}
}
public override string ToString()
{
return string.Format("PackageName: {0}, Version: {1}", PackageName??"", Version ?? "");
}
CompareTo - see the documentation. By using ToString() you get a comparison on the package name and then the version without having to do your own checks. Just make sure that ToString is correct (I added null check so it does not throw an Exception).
GetHashCode - not sure where you got your implementation from. You need to make sure that the hashcode is always unique for that item unless they truely are equal. This is the implementation I found on this previous answer on SO, see the last edit in the answer..
Your CompareTo method should work as this:
return -1 if this is smaller than obj
return 1 if this is bigger than obj and
most important: return 0 if this equals obj

Categories