List.Sort by property name - Including subclasses - c#

I have a bit of a challenge. I need to sort a List of objects, and I need to sort it from a string representing the path to the property in any sub class.
I need to use the List.Sort() and not OrderBy().
Lest make a simple example. I have a list of persons represented by two sub classes for identification and name
public class NameParts
{
public String FirstName { get; set; }
public String LastName { get; set; }
}
public class Identification
{
public String NiNumber { get; set; }
public NameParts Name { get; set; }
}
public class Person
{
public String Email { get; set; }
public String Phone { get; set; }
public Int16 Age { get; set; }
public Identification Id { get; set; }
}
Now I need to sort the list by age. Very simple
public static void SortByAge(List<Person> listToSort)
{
listToSort.Sort((x, y) => x.Age.CompareTo(y.Age));
}
And even by NiNumber and FirstName it is fairly simple this way
public static void SortByNiNumber(List<Person> listToSort)
{
listToSort.Sort((x, y) => x.Id.NiNumber.CompareTo(y.Id.NiNumber));
}
public static void SortByFirstName(List<Person> listToSort)
{
listToSort.Sort((x, y) => x.Id.Name.FirstName.CompareTo(y.Id.Name.FirstName));
}
Now comes the tricky part. I need to perform all the above sorts giving a string that represents the path to theproperty to sort by.
Like "Id.Name.FirstName"
So I need
public static void SortByAny(List<Person> listToSort, String sortBy)
{
//??????
}
That can be called with
List<Person> theList = new List<Person>();
SortByAny(theList, "Age");
SortByAny(theList, "Id.NiNumber");
SortByAny(theList, "Id.Name.FirstName");
I know I need to use reflection for this, and I have managed to do so but I cannot get further than properties in the Person Class itself, so I probably need to do something else, and this is where I'm stuck.
Does anyone have some brilliant ideas on how to solve this?
Thanks

You can modify the approach #E.Mourits linked: C# dynamic. String property path.
I included a bit of error checking, on error you have to check the InnerException of the InvalidOperationException the Sort method can throw.
static void SortByAny<T>(List<T> list, string path)
{
list.Sort((x, y) => ReflectOnPath(x, path).CompareTo(ReflectOnPath(y, path)));
}
static IComparable ReflectOnPath(object o, string path)
{
object value = o;
var pathComponents = path.Split('.');
foreach (var component in pathComponents)
{
if (value == null)
{
throw new NullReferenceException($"Path '{path}' can not be resolved at: {component}.");
}
var prop = value.GetType().GetProperty(component);
if (prop == null)
{
throw new ArgumentException($"Path '{path}' can not be resolved at: {component}.", nameof(path));
}
value = prop.GetValue(value, null);
}
if (!(value is IComparable))
{
throw new ArgumentException($"Value at path '{path}' does not implement ICompareable.", nameof(path));
}
return (IComparable)value;
}
If some of the values you want to compare do not implement IComparable you have to add more details how you want to compare them in this case.

Do you really need to put reflection in place? If u need to sort the list by a specific and determined number of "paths" i would suggest you implementing it in a fixed way, going straight ahead with sorting. maybe a switch might help?
switch(sortByString){
case: "Id.NiNumber":
SortByNiNumber(List<Person> listToSort);
break;
...
}
if you don't have too many options it would be faster. And maybe you can replace the switch with a dictionary of search paths and delegates or actions.

Because getting properties by reflection is quite hard to do, just use and modify this code:
public static void SortByAny(List<Person> listToSort, String sortBy)
{
if (sortBy == "Email")
listToSort.Sort((x, y) => x.Email.CompareTo(y.Email));
else if (sortBy == "Phone")
listToSort.Sort((x, y) => x.Phone.CompareTo(y.Phone));
else if (sortBy == "Age")
listToSort.Sort((x, y) => x.Age.CompareTo(y.Age));
else if (sortBy == "Id.NiNumber")
listToSort.Sort((x, y) => x.Id.NiNumber.CompareTo(y.Id.NiNumber));
else if (sortBy == "Id.Name.FirstName")
listToSort.Sort((x, y) => x.Id.Name.FirstName.CompareTo(y.Id.Name.FirstName));
else if (sortBy == "Id.Name.LastName")
listToSort.Sort((x, y) => x.Id.Name.LastName.CompareTo(y.Id.Name.LastName));
}

Related

Iterate through Nested Classes and Transform Property, Multiple Int by 2

The goal of this code is to iterate through multiple nested classes, and multiple any integer by 2. Provided simple example, however, example will be more complicated in future.
How do I change a Object to its underlying class? When I iterate through this function, it reads the type for OuterProduct correctly, but fails for InnerProduct reading as type System.RuntimeType, giving an error below
How can I resolve this code to multiply all nested integers by 2?
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
class Program
{
static void Main(string[] args)
{
var test = new OuterProduct();
test.AmountSold = 5;
test.ProductName = "BookOuter";
test.InnerProduct = new InnerProduct();
test.InnerProduct.ProductNameInner = "BookInner";
test.InnerProduct.AmountSoldInner = 7;
ReadPropertiesTest.ReadPropertiesRecursive(test);
}
}
public class OuterProduct
{
public string ProductName { get; set; }
public int AmountSold { get; set; }
public InnerProduct InnerProduct { get; set; }
}
public class InnerProduct
{
public string ProductNameInner { get; set; }
public int AmountSoldInner { get; set; }
}
public static class ReadPropertiesTest
{
public static void ReadPropertiesRecursive(object test)
{
var type = test.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
if (property.PropertyType == typeof(int) || property.PropertyType == typeof(int?))
{
property.SetValue(test, (int)(property.GetValue(test)) * 2);
}
if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
ReadPropertiesRecursive(property.PropertyType);
}
}
}
}
Resources:
C#: How to get all public (both get and set) string properties of a type
How to iterate through nested properties of an object
System.RuntimeType is the implementation of the class that represents typeof(X) or something.GetType(). When you pass PropertyType to your function you are not passing the property value, but it's type.
You will need to pass the next object in the hierarchy into the recursive function by using GetValue.
Note though that this is dangerous and error prone. For example, if you have a List<> property you obviously cannot increase its Count (it is readonly!). You should check to make sure that the property can be written to using the CanWrite property.
You also need to check for null objects. On top of that we need to handle int differently from int? (otherwise casting null to int will throw). The latter we can clean up a bit with c#7 pattern matching:
public static void ReadPropertiesRecursive(object test)
{
if (test is null) // base case
return;
var type = test.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
// check if we can even read the property
if(!property.CanRead)
continue;
// use pattern matching on the value
// nulls will be ignored
// we *could* cache GetValue but then it means we will invoke it for uninteresting types/properties
// it's also why I don't call GetValue until we've inspected PropertyType
if (property.CanWrite &&
(property.PropertyType == typeof(int) || property.PropertyType == typeof(int?)) &&
property.GetValue(test) is int i)
{
property.SetValue(test, i * 2);
}
else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
ReadPropertiesRecursive(property.GetValue(test));
}
}
}
An alternative version that omits some of the checks against PropertyType can also be used. It's a bit cleaner looking but it could potentially perform the GetValue reflection in cases where we don't need/want it (like on a double or a struct):
public static void ReadPropertiesRecursive(object test)
{
if (test is null) // base case
return;
var type = test.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
// check if we can even read the property
if(!property.CanRead)
continue;
// possibly unnecessary if not int or class
var val = property.GetValue(test);
if (property.CanWrite && val is int i)
{
property.SetValue(test, i * 2);
}
else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
ReadPropertiesRecursive(val);
}
}
}
Note that you may want to have a whitelist or blacklist of types. Recursing into a Type object for example isn't going to get you much.
Alternative would be to go with more object-oriented approach. Make it responsibility of every class which need to be "updated".
For every type with properties which need to be updated introduce a method to do it.
public class OuterProduct
{
public string ProductName { get; set; }
public int AmountSold { get; set; }
public InnerProduct InnerProduct { get; set; }
public void Update()
{
AmountSold *= 2;
InnerProduct.Update();
}
}
public class InnerProduct
{
public string ProductNameInner { get; set; }
public int AmountSoldInner { get; set; }
public void Update()
{
AmountSoldInner *= 2;
}
}
// Usage is simple
var test = new OuterProduct
{
AmountSold = 5,
ProductName = "BookOuter",
InnerProduct = new InnerProduct
{
ProductNameInner = "BookInner",
AmountSoldInner = 7
}
};
test.Update();
// test.AmountSold == 10 is true
// test.InnerProduct.AmountSoldInner == 14 is true
This approach will simplify code maintenance. For example adding/removing properties or worse case scenario adding some other logic to Update method will be isolated in one class.
In your recursive call you are passing the type, not the actual property value:
if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
ReadPropertiesRecursive(property.PropertyType);
}
should be:
if (property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))
{
ReadPropertiesRecursive(property.GetValue(test));
}

Get list of distinct values in List<T> in c#

So, say I have something like the following:
public class Element
{
public int ID;
public int Type;
public Properties prorerty;
...
}
and
public class Properties
{
public int Id;
public string Property;
...
}
and I have a list of these:
List Elements = new List();
What would be the cleanest way to get a list of all distinct values in the prorerty column in Element class? I mean, I could iterate through the list and add all values that aren't duplicates to another list of strings, but this seems dirty and inefficient. I have a feeling there's some magical Linq construction that'll do this in one line, but I haven't been able to come up with anything.
var results = Elements.Distinct();
Note: you will have to override .Equals and .GetHashCode()
public class Element : IEqualityComparer<Element>
{
public bool Equals(Element x, Element y)
{
if (x.ID == y.ID)
{
return true;
}
else
{
return false;
}
}
}
public int GetHashCode(Element obj)
{
return obj.ID.GetHashCode();
}
Isn't simpler to use one of the approaches shown below :) ? You can just group your domain objects by some key and select FirstOrDefault like below.
This is a copy of my answer on similar question here:
Get unique values - original answer
More interesting option is to create some Comparer adapter that takes you domain object and creates other object the Comparer can use/work with out of the box. Base on the comparer you can create your custom linq extensions like in sample below. Hope it helps :)
[TestMethod]
public void CustomDistinctTest()
{
// Generate some sample of domain objects
var listOfDomainObjects = Enumerable
.Range(10, 10)
.SelectMany(x =>
Enumerable
.Range(15, 10)
.Select(y => new SomeClass { SomeText = x.ToString(), SomeInt = x + y }))
.ToList();
var uniqueStringsByUsingGroupBy = listOfDomainObjects
.GroupBy(x => x.SomeText)
.Select(x => x.FirstOrDefault())
.ToList();
var uniqueStringsByCustomExtension = listOfDomainObjects.DistinctBy(x => x.SomeText).ToList();
var uniqueIntsByCustomExtension = listOfDomainObjects.DistinctBy(x => x.SomeInt).ToList();
var uniqueStrings = listOfDomainObjects
.Distinct(new EqualityComparerAdapter<SomeClass, string>(x => x.SomeText))
.OrderBy(x=>x.SomeText)
.ToList();
var uniqueInts = listOfDomainObjects
.Distinct(new EqualityComparerAdapter<SomeClass, int>(x => x.SomeInt))
.OrderBy(x => x.SomeInt)
.ToList();
}
Custom comparer adapter:
public class EqualityComparerAdapter<T, V> : EqualityComparer<T>
where V : IEquatable<V>
{
private Func<T, V> _valueAdapter;
public EqualityComparerAdapter(Func<T, V> valueAdapter)
{
_valueAdapter = valueAdapter;
}
public override bool Equals(T x, T y)
{
return _valueAdapter(x).Equals(_valueAdapter(y));
}
public override int GetHashCode(T obj)
{
return _valueAdapter(obj).GetHashCode();
}
}
Custom linq extension (definition of DistinctBy extension method):
// Embed this class in some specific custom namespace
public static class DistByExt
{
public static IEnumerable<T> DistinctBy<T,V>(this IEnumerable<T> enumerator,Func<T,V> valueAdapter)
where V : IEquatable<V>
{
return enumerator.Distinct(new EqualityComparerAdapter<T, V>(valueAdapter));
}
}
Definition of domain class used in test case:
public class SomeClass
{
public string SomeText { get; set; }
public int SomeInt { get; set; }
}
var props = Elements.Select(x => x.Properties).Distinct();
And make sure you overridden .Equals() and .GetHashCode() methods.
Or if you need direct strings from Properties:
var props = Elements
.Select(x => x.Properties != null ? x.Properties.Property : null)
.Distinct();
If you need the string fields on the Properties field, and if you know the Properties field prorerty is never null, just use
IEnumerable<string> uniqueStrings = Elements
.Select(e => e.prorerty.Property).Distinct();
If there's a chance prorerty can be null, handle that situation in the lambda.
This will use the default equality comparer for String which is an ordinal comparison independent of culture and case-sensitive.
my working example from LINQPad (C# Program)
void Main()
{
var ret = new List<Element>();
ret.Add(new Element(){ID=1});
ret.Add(new Element(){ID=1});
ret.Add(new Element(){ID=2});
ret = ret.GroupBy(x=>x.ID).Select(x=>x.First()).ToList();
Console.WriteLine(ret.Count()); // shows 2
}
public class Element
{
public int ID;
public int Type;
public Properties prorerty;
}
public class Properties
{
public int Id;
public string Property;
}

Is there a way to return different keySelectors to one OrderBy?

I have a long line with an OrderBy inside. This line repeats itself several times with only a minor difference:
OrderBy(m => m.Name)
OrderBy(m => m.CreationTime)
etc.
In the interest of maintainability, is there a way to make only one line, and have it call some method, or perhaps use some Action that will take care of returning the correct field? The problem is that the fields are not of the same type.
Look at this method signature:
public static IOrderedEnumerable OrderBy(
this IEnumerable source,
Func keySelector,
IComparer comparer
)
You need to give it IComparer<TKey>
data.OrderBy(x => x, new FooComparer());
data.OrderBy(x => x, new BarComparer());
data.OrderBy(x => x, new BazComparer());
Not sure if it's much cleaner to create classes just for sorting...
You can also create different methods that sort the list according to an enum they get as a parameter,
I think it's a lot cleaner solution.
MSDN
Maybe this comes closer to what you want
public IComparable GetKey(int keyNum)
{
switch (keyNum) {
case 1:
return Name;
case 2:
return CreationTime;
default:
return null;
}
}
Sorting would occur like this
.OrderBy(m => m.GetKey(1))
Yet another way is to return a delegate
public static Func<MyClass, IComparable> GetKeySelector(int keyNum)
{
switch (keyNum) {
case 1:
return m => m.Name;
case 2:
return m => m.CreationTime;
default:
throw new ArgumentException();
}
}
.OrderBy(MyClass.GetKeySelector(1));
It sounds like you are copying the same entire selection multiple times? You would just need this:
.OrderBy(m=>m.Name)
.ThenBy(m=>m.CreationTime);
After OrderBy you must use ThenBy
.OrderBy(m => m.Name)
.ThenBy(m => m.CreationTime)
But this does not answer your question. Implement IComparable<Type_of_m> in the type of m, then you can compare with
.OrderBy(m => m)
class Type_of_m : IComparable<Type_of_m>
{
public string Name { get; set; }
public DateTime CreationTime { get; set; }
public int CompareTo(Type_of_m other)
{
int comp = Name.CompareTo(other.Name);
if (comp != 0) {
return comp;
}
return CreationTime.CompareTo(other.CreationTime);
}
}
If you want to be able to order the items in several ways, then use a Comparer as gdoron
explained. This is a good case for nested classes.
class MyClass
{
public string Name { get; set; }
public DateTime CreationTime { get; set; }
public static readonly IComparer<MyClass> NameComparer = new MyNameComparaer();
private class MyNameComparaer : IComparer<MyClass>
{
public int Compare(MyClass x, MyClass y)
{
return x.Name.CompareTo(y.Name);
}
}
public static readonly IComparer<MyClass> DateComparer = new MyDateComparer();
private class MyDateComparer : IComparer<MyClass>
{
public int Compare(MyClass x, MyClass y)
{
return x.CreationTime.CompareTo(y.CreationTime);
}
}
}
You can then order like this
.OrderBy(m => m, MyClass.NameComparer)
or like this
.OrderBy(m => m, MyClass.DateComparer)

How get an prop of Object key in a Dictionary C#?

I have this homework, I have only 1 problem, and I don't know the solution. We have this class, and we musn't create another variables or methods...
I have a beers dictionary with < Beer object, int income >. But the method has got only the Beer object's name (prop), not the object.
And I don't have another idea, how can I get the Beer object's name from a Dictionary
I have only 2 idea, but these don't work.
The first is I tried use a ContainsKey() method. The second is an foreach iteration
using System;
using System.Collections.Generic;
namespace PubBeer
{
public class Beer
{
string name;
int price;
double alcohol;
public string Name{ get { return name; } }
public int Price{ get; set; }
public double Alcohol{ get { return alcohol;} }
public Sör(string name, int price, double alcohol)
{
this.name= name;
this.price= price;
this.alcohol= alcohol;
}
public override bool Equals(object obj)
{
if (obj is Beer)
{
Beer other = (Beer)obj;
return this.name== other.name;
}
return false;
}
}
public class Pub
{
int income;
IDictionary<Beer, int> beers= new Dictionary<Beer, int>();
public int Income{ get; set; }
public int Sold(string beerName, int mug)
{
// Here the problem
beers; // Here I want to like this: beers.Contains(beerName)
// beers.ContainsKey(Object.Name==beerName) or someone like this
// foreach (var item in beers)
// {
// item.Key.Name== beerName;
// }
}
...
Use LINQ to query over the collection of Keys.
//Throws an error if none or more than one object has the same name.
var beer = beers.Keys.Single(b => b.Name == beerName);
beers[beer] = ...;
// -or -
//Selects the first of many objects that have the same name.
//Exception if there aren't any matches.
var beer = beers.Keys.First(b => b.Name == beerName);
beers[beer] = ...;
// -or -
//Selects the first or default of many objects.
var beer = beers.Keys.FirstOrDefault(b => b.Name == beerName);
//You'll need to null check
if (beer != null)
{
beers[beer] = ...;
}
// etc...
Update: NON-LINQ Alternative
Beer myBeer;
foreach (var beer in beers.Keys)
{
if (beer.Name == beerName)
{
myBeer = beer;
break;
}
}
if (myBeer != null)
{
beers[myBeer] = ...;
}
You could use Any() on the Keys collection:
if (beers.Keys.Any(x => x.Name == beerName))
{
}
In the worst case this would have to look through all beers though - if you usually look up beers by name you should consider making the beer name the key and the beer object itself the value in the dictionary.
Once you have identified that such a beer exists you can use First() to select it:
Beer myBeer = beers.First(x => x.Key.Name == beerName).Key;
try to use Keys property
beers.Keys.Where(p => p.name == beername )
or
beers.Keys.FirstOrDefault(p => p.name == beername)

IEnumerable.Except() and a custom comparer

I'm having troubles with the Except() method.
Instead of returning the difference, it returns the original set.
I've tried implementing the IEquatable and IEqualityComparer in the Account class.
I've also tried creating a separate IEqualityComparer class for Account.
When the Except() method is called from main, it doesn't seem to call my custom Equals() method, but when I tried Count(), it did call the custom GetHashCode() method!
I'm sure I made a trivial mistake somewhere and I hope a fresh pair of eyes can help me.
main:
IEnumerable<Account> everyPartnerID =
from partner in dataContext.Partners
select new Account { IDPartner = partner.ID, Name = partner.Name };
IEnumerable<Account> hasAccountPartnerID =
from partner in dataContext.Partners
from account in dataContext.Accounts
where
!partner.ID.Equals(Guid.Empty) &&
account.IDPartner.Equals(partner.ID) &&
account.Username.Equals("Special")
select new Account { IDPartner = partner.ID, Name = partner.Name };
IEnumerable<Account> noAccountPartnerID =
everyPartnerID.Except(
hasAccountPartnerID,
new LambdaComparer<Account>((x, y) => x.IDPartner.Equals(y.IDPartner)));
Account:
public class Account : IEquatable<Account>
{
public Guid IDPartner{ get; set; }
public string Name{ get; set; }
/* #region IEquatable<Account> Members
public bool Equals(Account other)
{
return this.IDPartner.Equals(other.IDPartner);
}
#endregion*/
}
LambdaComparer:
public class LambdaComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _lambdaComparer;
private readonly Func<T, int> _lambdaHash;
public LambdaComparer(Func<T, T, bool> lambdaComparer) :
this(lambdaComparer, o => o.GetHashCode())
{
}
public LambdaComparer(Func<T, T, bool> lambdaComparer, Func<T, int> lambdaHash)
{
if (lambdaComparer == null)
throw new ArgumentNullException("lambdaComparer");
if (lambdaHash == null)
throw new ArgumentNullException("lambdaHash");
_lambdaComparer = lambdaComparer;
_lambdaHash = lambdaHash;
}
public bool Equals(T x, T y)
{
return _lambdaComparer(x, y);
}
public int GetHashCode(T obj)
{
return _lambdaHash(obj);
}
}
Basically your LambdaComparer class is broken when you pass in just a single function, because it uses the "identity hash code" provider if you don't provide anything else. The hash code is used by Except, and that's what's causing the problem.
Three options here:
Implement your own ExceptBy method and then preferably contribute it to MoreLINQ which contains that sort of thing.
Use a different implementation of IEqualityComparer<T>. I have a ProjectionEqualityComparer class you can use in MiscUtil - or you can use the code as posted in another question.
Pass a lambda expression into your LambdaComparer code to use for the hash:
new LambdaComparer<Account>((x, y) => x.IDPartner.Equals(y.IDPartner)),
x => x.IDPartner.GetHashCode());
You could also quickly fix your LambdaComparer to work when only the equality parameters are supplied like this:
public LambdaComparer(Func<T, T, bool> lambdaComparer) :
this(lambdaComparer, o => 1)
{
}
Look here, how to use and implementing IEqualityComparer in way with linq.Except and beyond.
https://www.dreamincode.net/forums/topic/352582-linq-by-example-3-methods-using-iequalitycomparer/
public class Department {
public string Code { get; set; }
public string Name { get; set; }
}
public class DepartmentComparer : IEqualityComparer {
// equal if their Codes are equal
public bool Equals(Department x, Department y) {
// reference the same objects?
if (Object.ReferenceEquals(x, y)) return true;
// is either null?
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.Code == y.Code;
}
public int GetHashCode(Department dept) {
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
// if null default to 0
if (Object.ReferenceEquals(dept, null)) return 0;
return dept.Code.GetHashCode();
}
}
IEnumerable<Department> deptExcept = departments.Except(departments2,
new DepartmentComparer());
foreach (Department dept in deptExcept) {
Console.WriteLine("{0} {1}", dept.Code, dept.Name);
}
// departments not in departments2: AC, Accounts.
IMO, this answer above is the simplest solution compared to other solutions for this problem. I tweaked it such that I use the same logic for the Object class's Equals() and GetHasCode(). The benefit is that this solution is completely transparent to the client linq expression.
public class Ericsson4GCell
{
public string CellName { get; set; }
public string OtherDependantProperty { get; set; }
public override bool Equals(Object y)
{
var rhsCell = y as Ericsson4GCell;
// reference the same objects?
if (Object.ReferenceEquals(this, rhsCell)) return true;
// is either null?
if (Object.ReferenceEquals(this, null) || Object.ReferenceEquals(rhsCell, null))
return false;
return this.CellName == rhsCell.CellName;
}
public override int GetHashCode()
{
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
// if null default to 0
if (Object.ReferenceEquals(this, null)) return 0;
return this.CellName.GetHashCode();
}
}

Categories

Resources