check if algorithm "saw" the class before [duplicate] - c#

I am populating an array with instances of a class:
BankAccount[] a;
. . .
a = new BankAccount[]
{
new BankAccount("George Smith", 500m),
new BankAccount("Sid Zimmerman", 300m)
};
Once I populate this array, I would like to sort it by balance amounts. In order to do that, I would like to be able to check whether each element is sortable using IComparable.
I need to do this using interfaces. So far I have the following code:
public interface IComparable
{
decimal CompareTo(BankAccount obj);
}
But I'm not sure if this is the right solution. Any advice?

You should not define IComparable yourself. It is already defined. Rather, you need to implement IComparable on your BankAccount class.
Where you defined the class BankAccount, make sure it implements the IComparable interface. Then write BankAccount.CompareTo to compare the balance amounts of the two objects.
public class BankAccount : IComparable<BankAccount>
{
[...]
public int CompareTo(BankAccount that)
{
if (this.Balance < that.Balance) return -1;
if (this.Balance == that.Balance) return 0;
return 1;
}
}
Edit to show Jeffrey L Whitledge's solution from comments:
public class BankAccount : IComparable<BankAccount>
{
[...]
public int CompareTo(BankAccount that)
{
return this.Balance.CompareTo(that.Balance);
}
}

IComparable already exists in .NET with this definition of CompareTo
int CompareTo(Object obj)
You are not supposed to create the interface -- you are supposed to implement it.
public class BankAccount : IComparable {
int CompareTo(Object obj) {
// return Less than zero if this object
// is less than the object specified by the CompareTo method.
// return Zero if this object is equal to the object
// specified by the CompareTo method.
// return Greater than zero if this object is greater than
// the object specified by the CompareTo method.
}
}

Do you want to destructively sort the array? That is, do you want to actually change the order of the items in the array? Or do you just want a list of the items in a particular order, without destroying the original order?
I would suggest that it is almost always better to do the latter. Consider using LINQ for a non-destructive ordering. (And consider using a more meaningful variable name than "a".)
BankAccount[] bankAccounts = { whatever };
var sortedByBalance = from bankAccount in bankAccounts
orderby bankAccount.Balance
select bankAccount;
Display(sortedByBalance);

An alternative is to use LINQ and skip implementing IComparable altogether:
BankAccount[] sorted = a.OrderBy(ba => ba.Balance).ToArray();

There is already IComparable<T>, but you should ideally support both IComparable<T> and IComparable. Using the inbuilt Comparer<T>.Default is generally an easier option. Array.Sort, for example, will accept such a comparer.

If you only need to sort these BankAccounts, use LINQ like following
BankAccount[] a = new BankAccount[]
{
new BankAccount("George Smith", 500m),
new BankAccount("Sid Zimmerman", 300m)
};
a = a.OrderBy(bank => bank.Balance).ToArray();

If you need to compare multiple fields, you can get some help from the compiler by using the new tuple syntax:
public int CompareTo(BankAccount other) =>
(Name, Balance).CompareTo(
(other.Name, other.Balance));
This scales to any number of properties, and it will compare them one-by-one as you would expect, saving you from having to implement many if-statements.
Note that you can use this tuple syntax to implement other members as well, for example GetHashCode. Just construct the tuple and call GetHashCode on it.

This is an example to the multiple fields solution provided by #Daniel Lidström by using tuple:
public static void Main1()
{
BankAccount[] accounts = new BankAccount[]
{
new BankAccount()
{
Name = "Jack", Balance =150.08M
}, new BankAccount()
{
Name = "James",Balance =70.45M
}, new BankAccount()
{
Name = "Mary",Balance =200.01M
}, new BankAccount()
{
Name = "John",Balance =200.01M
}};
Array.Sort(accounts);
Array.ForEach(accounts, x => Console.WriteLine($"{x.Name} {x.Balance}"));
}
}
public class BankAccount : IComparable<BankAccount>
{
public string Name { get; set; }
public int Balance { get; set; }
public int CompareTo(BankAccount other) =>
(Balance,Name).CompareTo(
(other.Balance,other.Name ));
}
Try it

Related

Not getting properties on generic parameter T when trying to create generic comparer to compare 2 objects

I want to compare 2 objects like below :
I don't want 2 employees in the same department
I don't want 2 Animals in the same Zoo
So on.....
I am trying to implement IEqualityComparer to accept generic type argument to take Employee or Animals or any object and to the comparison for me but I ran into a problem because since I have T as an argument, I am not getting the properties on T.
public class GenericComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
// Not getting a property here T.??
//now if I would have T as an Employee then I could do something like this here:
// return String.Equals(x.Name, y.Name); // but this will work because I know that Name is string
// but lets say if I have a different datatype that I want to compare then how can i do that?
}
public int GetHashCode(T obj)
{
return obj.
}
}
The thing is I don't want to create 2 classes like EmployeeComparer: IEqualityComparer<Employee> and AnimalComparer : IEqualityComparer<Animal> as the code will be somewhat similar.
Is it possible to create generic compared to compare any objects of the same types?
Update:
I am just trying to understand the limitations of generics with reference types. When should we create a Generic class or methods to accept reference types and when we should not.
I was thinking that since List<T> can accept anything like List<Employee> or List<Animal> or anything then why my GenericComparer cannot.
Because when we create List<Employee> then we can run for each loop and access the properties right:
foreach(var employee in employees)
{
string name = employee.Name; //Here we are able to access the properties
}
Then how come not in my case?
Answer to all the above questions will help me understand generics better with reference type especially. So, if someone can provide an answer to all this question and why it is not possible with my GenericComaprer<T> and how it is possible with List<T> then I will really appreciate :)
when we create List<Employee> then we can run for each loop and access the properties
Sure, because you are using List<T> by providing it the concrete type (Employee) to use in place for T. But if you were to look at the internals of (or try to write your own version of) List<T> you'd see you have no way to access an hypothetical Name property of T. That is because C# is a strictly typed language (contrarily to Javascript or Python for example). It won't let you use anything it doesn't know beforehand and for sure exists.
The way you defined your GenericComparer<T> class, all the compiler know is that T can do what every C# objects can, that is... not much (you can use ToString() and GetType() and compare references, that's pretty much all).
Then, because otherwise generics wouldn't be very useful, you can specify constraints on your generic type: you can, for example, tell the compiler that T must implement some interface. If you do this, then you can access any member defined in this interface on your T instances from inside the generic class.
For the sake of the example, let's say you have employees and animals; both employees and animals have a Name, then employees also have a Salary but animals have a Species. This could look like this:
public class Employee {
public string Name { get; set; }
public double Salary { get; set; }
}
public class Animal {
public string Name { get; set; }
public string Species { get; set; }
}
If we keep it that way, as explained above, you won't be able to access the properties from your generic class. So let's introduce an interface:
public interface IHasName {
string Name { get; }
}
And add : IHasName at the end of the Employee and Animal class declarations.
We also need to tweak GenericComparer declaration this way:
public class GenericComparer<T> : IEqualityComparer<T> where T : IHasName
By doing this, we tell the compiler that the T in GenericComparer must implement IHasName. The benefit is now you can access the Name property from within GenericComparer. On the other side, you won't be able to use GenericComparer by passing it anything that doesn't implement IHasName. Also note that the interface only defines the ability to get the Name property, not to set it. So, although you can of course set the Name on Employee and Animal instances, you won't be able to do so from inside GenericComparer.
With the definitions above, here is an example of how you could write the GenericComparer<T>.Equals method:
public bool Equals(T x, T y)
{
// Just a convention: if any of x or y is null, let's say they are not equal
if (x == null || y == null) return false;
// Another convention: let's say an animal and an employee cannot be equal
// even if they have the same name
if (x.GetType() != y.GetType()) return false;
// And now if 2 employees or animals have the same name, we'll say they are equal
if (x.Name == y.Name) return true;
return false;
}
All that said, I don't know exactly what your use case for GenericComparer<T> is (maybe you need this in a Dictionary or some other method that requires a way to compare instances... Anyway, as other commenters have stated, the proper way to go would probably be to:
override Equals and GetHashCode
provide == and != operators
implement IEquatable<T>
Do this on all classes you want to have a custom implementation of equality.
If you happen to use a recent version of Visual Studio (I'm using VS 2019 16.8 at the moment), you can automatically do all of this by right-clicking the class name, choosing Quick Actions and Refactorings > Generate Equals and GetHashCode. You will be presented with a window that allows you to choose the properties that should be part of the equality logic and you can also optionally choose to implement IEquatable<T> and generate == and !=. Once done, I'd recommend you review the generated code and make sure you understand what it does.
By the way, if you do so, you'll notice that the generated code uses EqualityComparer<Employee>.Default. This is pretty much what you tried to implement by yourself with your GenericEqualityComparer.
Now to the part of your questions relating to reference types. I'm not sure I understand your questions for I don't see an obvious link between generic types and references. Generic types can act just as well on both reference and value types.
What bothers you is maybe the fact that equality does not work the same way in reference and value types:
For reference types, the default way the compiler considers things equal is to look at their references. If the references are the same, then the things are considered the same. To put it differently suppose you create 2 instances of an Employe class and feed them with exactly the same Name and Salary. Because they are distinct objects (with distinct places in memory, that is different references), emp1 == emp2 will return false.
In the case of value types (suppose Employee is a struct and not anymore a class), the compiler does something else: it compares all the properties of the struct and decides based upon their content whether the 2 employees are equal or not. In this case, emp1 == emp2 will return true. Note that here, the compiler (or rather the .NET runtime) is doing something similar to what you attempt to do with your universal comparer. However, it only does so for value types and it is rather slow (this is why one should often implement IEquatable and override Equals and GetHashcode on structures).
Well, I'm not sure I've answered all your questions, but if you want to know more, you should definitely go through some C# tutorials or documentation to understand more about reference vs value types and equality (even before you jump into implementing your own generic types).
What you're expecting of generics just isn't right:
I was thinking that since List<T> can accept anything like List<Employee> or List<Animal> or anything then why my GenericComparer cannot.
foreach(var employee in employees)
{
string name = employee.Name; //Here we are able to access the properties
}
Here you say that you can access the properties, but that's specifically because the list employees was instantiated as List<Employee>. The implementation of List however could Not access those properties, because it only sees T !
There are some ways to achieve what you want, but you'd have to consider design and performance pros/cons depending on your specific use-case and needs.
Here's what you could do:
public abstract class CustomComparable
{
// Force implementation to provide a comparison value
public abstract object GetComparisonValue();
}
public class Employee: CustomComparable
{
public int Department { get; set; }
public override object GetComparisonValue()
{
return Department;
}
}
public class Animal : CustomComparable
{
public string Zoo { get; set; }
public override object GetComparisonValue()
{
return Zoo;
}
}
public class CustomComparer<T> : IEqualityComparer<T> where T: CustomComparable
{
public bool Equals(T x, T y)
{
return x != null && y != null && x.GetComparisonValue().Equals(y.GetComparisonValue());
}
public int GetHashCode(T obj)
{
return obj.GetComparisonValue().GetHashCode();
}
}
Then you'd get this, for example:
class Program
{
static void Main(string[] args)
{
Animal cat = new Animal() { Zoo = "cat zoo" };
Animal dog = new Animal() { Zoo = "dog zoo" };
Animal puppy = new Animal() { Zoo = "dog zoo" };
List<Animal> animals = new List<Animal>() { cat, dog, puppy };
CustomComparer<Animal> animalComparer = new CustomComparer<Animal>();
Console.WriteLine($"Distinct zoos ? {animals.Distinct(animalComparer).Count() == animals.Count}");
Employee bob = new Employee() { Department = 1 };
Employee janet = new Employee() { Department = 2 };
Employee paul = new Employee() { Department = 3 };
List<Employee> employees = new List<Employee>() { bob, janet, paul };
CustomComparer<Employee> employeeComparer = new CustomComparer<Employee>();
Console.WriteLine($"Distinct departments ? {employees.Distinct(employeeComparer).Count() == employees.Count}");
}
}
> Distinct zoos ? False
> Distinct departments ? True
With all that being said, do use IEquatable<T>. The specific use of such equality implementations seems out of the scope of your initial question however, and many other resources and Q&A's can help you with these.
What you describe is easily possible, just use delegates.
public class GenericComparer<T, TValue> : IEqualityComparer<T>
{
private Func<T, TValue> _getter;
private IEqualityComparer<TValue> _valueComparer;
public GenericComparer(Func<T, TValue> getter, IEqualityComparer<TValue> valueComparer = null)
{
_getter = getter;
_valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
}
public bool Equals(T x, T y)
{
return _valueComparer.Equals(_getter(x), _getter(y));
}
public int GetHashCode(T obj)
{
return _valueComparer.GetHashCode(_getter(obj));
}
}
To use it, simply tell it by which property you want it to compare:
var animalByNameComparer = new GenericComparer<Animal, string>(an => an?.Name);
Of course, you can also handle the null case already in the generic comparer, also.
To your second question
The simple difference between List<T> and List<Employee> is that the former is an open generic type while the latter is a closed generic type. If you define a generic type, you always define it as an open generic type, so the compiler does not know what T will be. If you consume a generic type, you often work with closed generic types, i.e. the placeholder T already has a value and therefore, the compiler is able to resolve a symbolic name like .Name to a property. If you use the generic type in an open generic method (a method with a type parameter), you also cannot bind symbols.

How to sort object property in list by first two characters then alpha

I have a list of objects. The objects are insurance policies. Well, the first part of every policy number is alpha charater, then all numeric. I need to sort all policies with policy number starting with CA first in the list, then by alpha numeric after that. For example, if I have three policies, AB10001, CA20001, CA20003, the order should be all "CA" first, then sort rest like below:enter code here
CA20001
CA20003
AB10001
What is throwing me off, as I understand regular sorts, is how to get all CA first since they do not apply to alpha order. I think possibly, pull all of the CA prefixed policy names into a new list, then order those by numeric. Then sort the leftovers in a second list. Then append the second list to the "CA" sorted list if that makes sense. There must be a cleaner way though using lambda.
You can achieve that with something like.
var list = new List<string> { "AB10001", "CA20003", "CA20001" };
var ordered = list.OrderByDescending(s => s.StartsWith("CA")).ThenBy(s => s);
foreach(var o in ordered) Console.WriteLine(o);
which outputs
CA20001
CA20003
AB10001
Basically you first order everything by whether or not it begins with "CA" then by the actual string value. You have to use descending because false is considered to be less than true.
You can try the following:
const string CAHeader = "CA";
IEnumerable<string> sorted =
list.OrderBy(s => s.StartsWith(CAHeader) ? s.Substring(CAHeader.Length) : s);
Implement IComparer and put custom logic in Compare.
Here's an IComparer implementation. It's about 1200% more lines of code than juharr's solution. But it could be handy in certain scenarios that I haven't thought of yet.
The comparer:
public class PolicyComparer : IComparer<Policy>
{
public static PolicyComparer Instance { get; } = new PolicyComparer();
public int Compare(Policy x, Policy y)
{
return x.PolicyNumber.StartsWith("CA") ^ y.PolicyNumber.StartsWith("CA")
? (x.PolicyNumber.StartsWith("CA") ? -1 : 1)
: string.Compare(x.PolicyNumber, y.PolicyNumber, StringComparison.Ordinal);
}
}
An extension for sorting:
public static class PolicyExtensions
{
public static IEnumerable<Policy> Sort(this IEnumerable<Policy> unsortedPolicies)
{
var sorted = new List<Policy>(unsortedPolicies);
sorted.Sort(PolicyComparer.Instance);
return sorted;
}
}
The Policy class I used for testing:
public class Policy
{
public string PolicyNumber { get; set; }
}
A unit test:
[TestMethod]
public void PoliciesAreSortedByCAfirst()
{
var unsorted = new Policy[]
{
new Policy {PolicyNumber = "AB10001"},
new Policy {PolicyNumber = "CA20003"},
new Policy {PolicyNumber = "CA20001"}
};
var sorted = unsorted.Sort();
var expectedOrder = new string[] {"CA20001", "CA20003", "AB10001"};
Assert.IsTrue(expectedOrder.SequenceEqual(sorted.Select(policy=>policy.PolicyNumber)));
}
I was looking at this wrong. What needs to be sortable isn't the policy. It's the policy number. It's actually not a bad idea to declare the policy number as a separate class rather than using a string. The reason is that you can "hide" its implementation. Maybe at some point a policy "number" becomes a combination of two fields.
That makes even more sense because the policy number has its own logic that has nothing to do with Policy. It has a custom sort order. It probably has its own rules for validation. If it's just a string attached to a Policy then Policy has to own all that. And it's not unrealistic that you might want to put a policy number on something that isn't itself a policy, like a claim against a policy. (This is the sort of ID that probably shows up everywhere.)
Here's a solution from that angle:
public class PolicyNumber : IComparable
{
private readonly string _number;
public PolicyNumber(string number)
{
_number = number;
}
public int CompareTo(object obj)
{
var other = obj as PolicyNumber;
if (other == null) return 0;
return _number.StartsWith("CA") ^ other._number.StartsWith("CA")
? (_number.StartsWith("CA") ? -1 : 1)
: string.Compare(_number, other._number, StringComparison.Ordinal);
}
public override string ToString()
{
return _number;
}
public override bool Equals(object obj)
{
var other = obj as PolicyNumber;
return other != null && string.Equals(_number, other._number);
}
protected bool Equals(PolicyNumber other)
{
return string.Equals(_number, other._number);
}
public override int GetHashCode()
{
return (_number != null ? _number.GetHashCode() : 0);
}
}
And the unit test:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void PoliciesAreSortedByCAfirst()
{
var unsorted = new Policy[]
{
new Policy{Number = new PolicyNumber("AB10001")},
new Policy{Number = new PolicyNumber("CA20003")},
new Policy{Number = new PolicyNumber("CA20001")}
};
var sorted = unsorted.OrderBy(policy => policy.Number);
var expectedOrder = new Policy[]
{
unsorted[2], unsorted[1], unsorted[0]
};
Assert.IsTrue(expectedOrder.SequenceEqual(sorted));
}
}
Notice how that allows PolicyNumber to "own" its own logic. Classes that use it just sort it without knowing or caring how it is sorted. And you can still compare it for equality just like a string.
The unit test shows how easy that is to use. Instead of asking something to sort it for you, you just sort it. If it's a Policy with a policy number you can sort it by the policy number or by something else.

List of Anonymous objects?

I have a class using the List class. When attempting to set up the Add function to add my anonymous type, all I get is errors.
I've been searching for hours and as best I can tell, every example I've seen is doing the same thing. So I don't understand what's wrong.
class fileHistory<Object> : List<Object>
{
public new void Add(DateTime ts, int st)
{
base.Add( new { timeStamp = ts; status = st;} );
}
}
You don't need a generic declaration in your class definition, and you also need to change semicolons to commas:
public class fileHistory : List<Object>
{
public new void Add(DateTime ts, int st)
{
base.Add( new { timeStamp = ts, status = st} );
}
}
Right, you can overwrite (not polymorphism! check I used the word overwrite instead of override) List<T>.Add(T) method, but I believe that you could solve your issue using composition instead of inheritance and your code will work flawlessly:
class fileHistory
{
private readonly List<object> _log = new List<object>();
public void Add(DateTime ts, int st)
{
_log.Add( new { timeStamp = ts; status = st;} );
}
}
BTW, I see three design flaws here:
Anonymous types aren't meant to your use case. If you are adding objects with these two properties and you do it in a concrete use case like yours, maybe you're using anonymous types because of your laziness of designing a class with 2 properties??? ;)
Because of #1, why you would create a list of objects using a generic list? It defeats the entire purpose of generics!!
I find a bad design decision hidding Add of List<T>. Use composition instead of inheritance in these cases. Also, I don't know why you're using identifier re-using with new keyword when C# supports method overloading. In List<T> there's no overload of Add with your input parameters...
My advise is: code isn't fancier because of using fancy syntactic sugar provided by C#. Sometimes you don't need it, and honestly, I believe this is the case.
For those who're worried about LINQ...
Any class might or might not implement IEnumerable<T>. The whole fileHistory class can be iterated with foreach or LINQ and its extension methods implementing IEnumerable<T>:
// Check that I dropped the lazy approach of using
// anonymous types!!
class fileHistory : IEnumerable<FileLog>
{
private readonly List<FileLog> _log = new List<FileLog>();
public IEnumerator<FileLog> GetEnumerator()
{
return _log.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _log.GetEnumerator();
}
public void Add(DateTime ts, int st)
{
_log.Add(new FileLog { timeStamp = ts; status = st;} );
}
}
...and now some class like this can be iterated even when using composition instead of inheritance:
new fileHistory().Where(log => log.DateTime < DateTime.Now);
class fileHistory<Object> : List<Object>
{
public new void Add(DateTime ts, int st)
{
// That's how it's supposed to be
base.Add(new { timeStamp = ts, status = st });
}
}
Hope I've helped!

Unnecessary cast to interface?

I'm using a factory to create IComparer<User> objects to sort a list of users.
I have 2 classes : Ascending and Descending, both implement IComparer<User>. Here's the code :
namespace Test
{
public class Program
{
public static void Main(string[] args)
{
List<User> users = new List<User>();
users.Add(new User("foo", "bar"));
// ...
IComparer<User> cmp = ComparerFactory.GetComparer("FirstName", true);
if (cmp != null)
{
users.Sort(cmp);
}
}
}
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public User(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
public class UserFirstNameComparer
{
public class Ascending : IComparer<User>
{
public int Compare(User u1, User u2)
{
return String.Compare(u1.FirstName, u2.FirstName, true);
}
}
public class Descending : IComparer<User>
{
public int Compare(User u1, User u2)
{
return new UserFirstNameComparer.Ascending().Compare(u1, u2) * -1;
}
}
}
public static class ComparerFactory
{
public static IComparer<User> GetComparer(string fieldName, bool ascending)
{
switch (fieldName)
{
case "FirstName":
return ascending ?
new UserFirstNameComparer.Ascending() : // ERROR IS HERE
new UserFirstNameComparer.Descending();
//...
}
return null;
}
}
But I get an error (line : new UserFirstNameComparer.Ascending() :) :
Type of conditional expression cannot be determined because there is no implicit conversion between 'Test.UserFirstNameComparer.Ascending' and 'Test.UserFirstNameComparer.Descending'
I dont understand what it means, both are IComparer objects, so what's the problem ?
The weird thing is that I can fix the error with a (unnecessary ?) cast :
// This works
return ascending ?
(IComparer<User>) new UserFirstNameComparer.Ascending() :
new UserFirstNameComparer.Descending();
// This works too
return ascending ?
new UserFirstNameComparer.Ascending() :
(IComparer<User>) new UserFirstNameComparer.Descending();
Of course it works when I cast in both cases. But I do not understand why it works with only one cast, and why it does not when there is no cast. Any ideas ?
(I'm using VS 2012, .NET v4.0.30319)
The expression
cond ? X : Y
just requires (simplified) that either the compile-time type of X is implicitly convertible to the compile-time type of Y, or vice versa. It is not going to search through all interfaces and base classes of the two types to try and find some "common" type of them. (Even if it did, how would it handle the case with multiple common interfaces?)
This is how the language is designed. It is not relevant what the return type of your method is, that cannot be taken into consideration when trying to resolve a ?: expression.
You found the solution already, to cast X and/or Y to the desired type, explicitly.
Having answered your question, I also have a suggestion on how you can do this without writing your two classes Ascending and Descending. You can create an IComparer<> like this:
return ascending
? Comparer<User>.Create((u1, u2) => String.Compare(u1.FirstName, u2.FirstName, true))
: Comparer<User>.Create((u1, u2) => String.Compare(u2.FirstName, u1.FirstName, true))
;
so no need (possibly) for those two classes of yours.
new UserFirstNameComparer.Ascending() returns an instance of Ascending.
new UserFirstNameComparer.Descending() returns an instance of Descending.
Even though both implement IComparer, the type of these instances is different. When you cast one of them to IComparer, the resulting object has type IComparer. Now it is possible to do an implicit conversion, since one is an interface and the other a class implementing it.
To quote the documentation, for the conditional expression
condition ?: first_expression : second_expression
Either the type of first_expression and second_expression must be the same, or an implicit conversion must exist from one type to the other.

Abstract an IEqualityComparer implementation or override the default comparer to use Distinct method

I'm trying to find a distinct List<Author> given a List<BlogPost> where each BlogPost has an Author property. I've found the Distinct() extension method in generics and I'm trying to use it. First, let me explain my loop and where I want to use it, then I'll explain my classes and where I'm having trouble.
Trying to use distinct here
public List<Author> GetAuthors() {
List<BlogPost> posts = GetBlogPosts();
var authors = new List<Author>();
foreach (var bp in posts) {
authors.Add(bp.Author);
}
return authors.Distinct().ToList();
}
Based on what I've read on MSDN, Distinct() either uses the default comparer or a passed in comparer. I was hoping (I obviosuly don't know if this is doable) to write a comparer in one spot and be able to use it for all of my classes since they all compare by the exact same equality operation (which compares the GUID property of each class).
All of my classes inherit from the BasePage class:
public class BasePage : System.Web.UI.Page, IBaseTemplate, IEquatable<IBaseTemplate>, IEqualityComparer<IBaseTemplate>
public class Author : BasePage
public class BlogPost : BasePage
My equals method implemented in BasePage compares the GUID property which is unique to each. When I call Distinct() on an Author it doesn't seem to work. Is there any way I can wrap up the comparer in one place and always be able to use it rather than having to write something like class AuhorComparer : IEqualityComparer<Auhor> since I'd then need to write the same thing for each class, every time I want to use Distinct(). Or can I override the default comparer somehow so I don't have to pass anything to Distinct()?
The Distinct operation is probably not the best solution here because you end up building a potentially very big list with duplicates only to then immediately shrink it to distinct elements. It's probably better to just start with a HashSet<Author> to avoid building up the large list.
public List<Author> GetAuthors() {
HashSet<Author> authorSet = new HashSet<Author>();
foreach (var author in GetBlogPosts().Select(x => x.Author)) {
authorSet.Add(author);
}
return authorSet.ToList();
}
If you do want to use Distinct then the best route is to implement IEquatable on the Author type. When not given an explicit IEqualityComparer the Distinct and other LINQ methods will eventually default into using the IEquatable implementation on the type. Usually through EqualityComprare<T>.Default
Overriden Equals should work for you. One thing that might be going wrong is that GetHashCode is not overridden alongside Equals, which the framework guidelines dictate should happen.
The code only shows the main idea, which, I hope, will be useful.
public class Repository
{
public List<Author> GetAuthors()
{
var authors = new List<Author>
{
new Author{Name = "Author 1"},
new Author{Name = "Author 2"},
new Author{Name = "Author 1"}
};
return authors.Distinct(new CustomComparer<Author>()).ToList();
}
public List<BlogPost> GetBlogPosts()
{
var blogPosts = new List<BlogPost>
{
new BlogPost {Text = "Text 1"},
new BlogPost {Text = "Text 2"},
new BlogPost {Text = "Text 1"}
};
return blogPosts.Distinct(new CustomComparer<BlogPost>()).ToList();
}
}
//This comparer is required only one.
public class CustomComparer<T> : IEqualityComparer<T> where T : class
{
public bool Equals(T x, T y)
{
if (y == null && x == null)
{
return true;
}
if (y == null || x == null)
{
return false;
}
if (x is Author && y is Author)
{
return ((Author)(object)x).Name == ((Author)(object)y).Name;
}
if (x is BlogPost && y is BlogPost)
{
return ((BlogPost)(object)x).Text == ((BlogPost)(object)y).Text;
}
//for next class add comparing logic here
return false;
}
public int GetHashCode(T obj)
{
return 0; // actual generating hash code should be here
}
}
public class Author
{
public string Name { get; set; }
}
public class BlogPost
{
public string Text { get; set; }
}

Categories

Resources