.NET 6 IntersectBy and ExceptBy examples - c#

Could someone provide me a small example on how to Use the .NET 6 LINQ IntersectBy and ExceptBy methods? MSDN hasn't got any examples and the one I tried doesn't compile due to CS0411 error. The example I tried:
namespace Test
{
internal struct Example
{
public int X { get; set; }
public int Y { get; set; }
public override string ToString()
{
return $"{X}, {Y}";
}
}
public class Program
{
public static void Main()
{
var elements = new List<Example>
{
new Example { X = 10, Y = 20 },
new Example { X = 11, Y = 23 },
};
var elements2 = new List<Example>
{
new Example { X = 10, Y = 12 },
new Example { X = 44, Y = 20 },
};
//ok
var union = elements.UnionBy(elements2, x => x.X);
//CS0411 - Why ?
var intersect = elements.IntersectBy(elements2, x => x.X);
//CS0411 - Why ?
var except = elements.ExceptBy(elements2, x => x.X);
Console.ReadKey();
}
}
}

Granted the documentation doesn't have any examples, it states that the selector function should select TKey i.e. the type of the second collection. The following should work:
var intersect = elements.IntersectBy(elements2, x => x);
var except = elements.ExceptBy(elements2, x => x);
Although I think this may be closer to what you want:
var intersect = elements.IntersectBy(elements2.Select(e => e.X), x => x.X);
For more complex types, you may want to consider implementing an IEqualityComparer and using the overloads that take one as an argument.

I made my own ExceptByProperty method like this
Usage:
var new = items.ExceptByProperty(duplicateItems, x => x.Id).ToList();
Code:
public static class EnumerableExtensions
{
public static IEnumerable<TSource> ExceptByProperty<TSource, TProperty>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TProperty> keySelector)
{
return first.ExceptBy(second, x => x, GenericComparer<TSource, TProperty>.Comparer(keySelector));
}
}
public sealed class GenericComparer<T, TProperty> : IEqualityComparer<T>
{
public static IEqualityComparer<T> Comparer(Func<T, TProperty> selector)
{
return new GenericComparer<T, TProperty>(selector);
}
private readonly Func<T, TProperty> selector;
public GenericComparer(Func<T, TProperty> selector)
{
this.selector = selector;
}
public bool Equals(T? x, T? y)
{
if (x == null || y == null) return false;
return Equals(selector(x), selector(y));
}
public int GetHashCode([DisallowNull] T obj)
{
object? value = selector(obj);
if (value == null) return obj.GetHashCode();
return value.GetHashCode();
}
}

Related

Get nested property for ordering from Expression<Func<T,Tkey>> [duplicate]

I'm creating a Validator<T> class. I'm attempting to implement the Linq SelectMany extension methods for my validator to be able to compose expressions using a Linq query and validate the final result even when the underlying values change.
The following test code demonstrates my intent.
var a = 2;
var b = 3;
var va = Validator.Create(() => a, n => n >= 0 && n < 5);
var vb = Validator.Create(() => b, n => n >= 0 && n < 5);
var vc = from ia in va
from ib in vb
select ia + ib;
Debug.Assert(vc.Value == a + b); //2 + 3
Debug.Assert(vc.Value == 5);
Debug.Assert(vc.IsValid == true);
a = 7;
Debug.Assert(vc.Value == a + b); //7 + 3
Debug.Assert(vc.Value == 10);
Debug.Assert(va.IsValid == false);
Debug.Assert(vb.IsValid == true);
Debug.Assert(vc.IsValid == false);
I've seen the following question How do I compose existing Linq Expressions which shows me how to compose two Func<T, bool>'s together using an And expression, but I need to be able to compose functions together in a more, well, functional way.
I have, for example, the following two expressions:
public Expression<Func<T>> ValueExpression { get; private set; }
public Expression<Func<T, bool>> ValidationExpression { get; private set; }
I wish to create a new expression like this:
public Expression<Func<bool>> IsValidExpression
{
get
{
// TODO: Compose expressions rather than compile & invoke.
}
}
More succinctly I'm trying to create these functions:
// Specific case
Func<Expression<Func<T>>, Expression<Func<T, bool>>, Expression<Func<bool>>>
// General case
Func<Expression<Func<X, Y>>, Expression<Func<Y, Z>>, Expression<Func<X, Z>>>
The general case function can be modified to accept different numbers of generic arguments as needed to compose any function.
I've searched Stack Overflow (of course) and the web, but haven't an example that solves this issue.
My code for the Validator<T> class is below.
public class Validator<T>
{
public Validator(Expression<Func<T>> valueFunc,
Expression<Func<T, bool>> validationFunc)
{
this.ValueExpression = valueFunc;
this.ValidationExpression = validationFunc;
}
public Expression<Func<T>> ValueExpression { get; private set; }
public Expression<Func<T, bool>> ValidationExpression { get; private set; }
public T Value { get { return this.ValueExpression.Compile().Invoke(); } }
public bool IsValid { get { return this.IsValidExpression.Compile().Invoke(); } }
public Expression<Func<bool>> IsValidExpression
{
get
{
// TODO: Compose expressions.
}
}
}
My SelectMany extensions contain loads of yucky .Compile().Invoke() which I want to get rid of.
public static Validator<U> SelectMany<T, U>(this Validator<T> #this, Expression<Func<T, Validator<U>>> k)
{
Expression<Func<T>> fvtv = #this.ValueExpression;
Expression<Func<Validator<U>>> fvu = () => k.Compile().Invoke(fvtv.Compile().Invoke());
Expression<Func<U>> fvuv = fvu.Compile().Invoke().ValueExpression;
Expression<Func<U, bool>> fvtiv = u => #this.ValidationExpression.Compile().Invoke(fvtv.Compile().Invoke());
return fvuv.ToValidator(fvtiv);
}
public static Validator<V> SelectMany<T, U, V>(this Validator<T> #this, Expression<Func<T, Validator<U>>> k, Expression<Func<T, U, V>> s)
{
Expression<Func<Validator<U>>> fvu = () => #this.SelectMany(k);
Expression<Func<T>> fvtv = #this.ValueExpression;
Expression<Func<U>> fvuv = fvu.Compile().Invoke().ValueExpression;
Expression<Func<T, bool>> fvtiv = #this.ValidationExpression;
Expression<Func<U, bool>> fvuiv = u => fvu.Compile().Invoke().ValidationExpression.Compile().Invoke(u);
Expression<Func<V>> fvv = () => s.Compile().Invoke(fvtv.Compile().Invoke(), fvuv.Compile().Invoke());
Expression<Func<V, bool>> fvviv = v => fvtiv.Compile().Invoke(fvtv.Compile().Invoke()) && fvuiv.Compile().Invoke(fvuv.Compile().Invoke());
return fvv.ToValidator(fvviv);
}
Thanks in advance!
While dtb's answer works for several scenarios, it is suboptimal as such an expression cannot be used in Entity Framework, as it cannot handle Invoke calls. Unfortunately, to avoid those calls one needs a lot more code, including a new ExpressionVisitor derived class:
static Expression<Func<A, C>> Compose<A, B, C>(Expression<Func<B, C>> f,
Expression<Func<A, B>> g)
{
var ex = ReplaceExpressions(f.Body, f.Parameters[0], g.Body);
return Expression.Lambda<Func<A, C>>(ex, g.Parameters[0]);
}
static TExpr ReplaceExpressions<TExpr>(TExpr expression,
Expression orig,
Expression replacement)
where TExpr : Expression
{
var replacer = new ExpressionReplacer(orig, replacement);
return replacer.VisitAndConvert(expression, nameof(ReplaceExpressions));
}
private class ExpressionReplacer : ExpressionVisitor
{
private readonly Expression From;
private readonly Expression To;
public ExpressionReplacer(Expression from, Expression to)
{
From = from;
To = to;
}
public override Expression Visit(Expression node)
{
return node == From ? To : base.Visit(node);
}
}
This replaces every instance of the first parameter in the first expression with the expression in the second expression. So a call like this:
Compose((Class1 c) => c.StringProperty, (Class2 c2) => c2.Class1Property
Would yield the expression (Class2 c2) => c2.Class1Property.StringProperty.
The equivalent of Haskell's function composition operator
(.) :: (b->c) -> (a->b) -> (a->c)
f . g = \ x -> f (g x)
would in C# probably be something like
static Expression<Func<A, C>> Compose<A, B, C>(
Expression<Func<B, C>> f,
Expression<Func<A, B>> g)
{
var x = Expression.Parameter(typeof(A));
return Expression.Lambda<Func<A, C>>(
Expression.Invoke(f, Expression.Invoke(g, x)), x);
}
Is this what you're looking for?
Example:
Compose<int, int, string>(y => y.ToString(), x => x + 1).Compile()(10); // "11"

Linq group by except column

I have a class with large amount of properties that I need to group by almost all columns.
class Sample {
public string S1 { get; set; }
public string S2 { get; set; }
public string S3 { get; set; }
public string S4 { get; set; }
// ... all the way to this:
public string S99 { get; set; }
public decimal? N1 { get; set; }
public decimal? N2 { get; set; }
public decimal? N3 { get; set; }
public decimal? N4 { get; set; }
// ... all the way to this:
public decimal? N99 { get; set; }
}
From time to time I need to group by all columns except one or two decimal columns and return some result based on this (namely object with all the fields, but with some decimal value as a sum or max).
Is there are any extension method that would allow me to do something like this:
sampleCollection.GroupByExcept(x => x.N2, x => x.N5).Select(....);
instead of specifying all columns in object?
You won't find anything builtin that handles such a case. You'd have to create one yourself. Depending on how robust you need this to be, you could take a number of approaches.
The main hurdle you'll come across is how you'll generate the key type. In an ideal situation, the new keys that are generated would have their own distinct type. But it would have to be dynamically generated.
Alternatively, you could use another type that could hold multiple distinct values and still could be suitably used as the key. Problem here is that it will still have to be dynamically generated, but you will be using existing types.
A different approach you could take that doesn't involve generating new types, would be to use the existing source type, but reset the excluded properties to their default values (or not set them at all). Then they would have no effect on the grouping. This assumes you can create instances of this type and modify its values.
public static class Extensions
{
public static IQueryable<IGrouping<TSource, TSource>> GroupByExcept<TSource, TXKey>(this IQueryable<TSource> source, Expression<Func<TSource, TXKey>> exceptKeySelector) =>
GroupByExcept(source, exceptKeySelector, s => s);
public static IQueryable<IGrouping<TSource, TElement>> GroupByExcept<TSource, TXKey, TElement>(this IQueryable<TSource> source, Expression<Func<TSource, TXKey>> exceptKeySelector, Expression<Func<TSource, TElement>> elementSelector)
{
return source.GroupBy(BuildKeySelector(), elementSelector);
Expression<Func<TSource, TSource>> BuildKeySelector()
{
var exclude = typeof(TXKey).GetProperties()
.Select(p => (p.PropertyType, p.Name))
.ToHashSet();
var itemExpr = Expression.Parameter(typeof(TSource));
var keyExpr = Expression.MemberInit(
Expression.New(typeof(TSource).GetConstructor(Type.EmptyTypes)),
from p in typeof(TSource).GetProperties()
where !exclude.Contains((p.PropertyType, p.Name))
select Expression.Bind(p, Expression.Property(itemExpr, p))
);
return Expression.Lambda<Func<TSource, TSource>>(keyExpr, itemExpr);
}
}
}
Then to use it you would do this:
sampleCollection.GroupByExcept(x => new { x.N2, x.N5 })...
But alas, this approach won't work under normal circumstances. You won't be able to create new instances of the type within a query (unless you're using Linq to Objects).
If you're using Roslyn, you could generate that type as needed, then use that object as your key. Though that'll mean you'll need to generate the type asynchronously. So you probably will want to separate this from your query all together and just generate the key selector.
public static async Task<Expression<Func<TSource, object>>> BuildExceptKeySelectorAsync<TSource, TXKey>(Expression<Func<TSource, TXKey>> exceptKeySelector)
{
var exclude = typeof(TXKey).GetProperties()
.Select(p => (p.PropertyType, p.Name))
.ToHashSet();
var properties =
(from p in typeof(TSource).GetProperties()
where !exclude.Contains((p.PropertyType, p.Name))
select p).ToList();
var targetType = await CreateTypeWithPropertiesAsync(
properties.Select(p => (p.PropertyType, p.Name))
);
var itemExpr = Expression.Parameter(typeof(TSource));
var keyExpr = Expression.New(
targetType.GetConstructors().Single(),
properties.Select(p => Expression.Property(itemExpr, p)),
targetType.GetProperties()
);
return Expression.Lambda<Func<TSource, object>>(keyExpr, itemExpr);
async Task<Type> CreateTypeWithPropertiesAsync(IEnumerable<(Type type, string name)> properties) =>
(await CSharpScript.EvaluateAsync<object>(
AnonymousObjectCreationExpression(
SeparatedList(
properties.Select(p =>
AnonymousObjectMemberDeclarator(
NameEquals(p.name),
DefaultExpression(ParseTypeName(p.type.FullName))
)
)
)
).ToFullString()
)).GetType();
}
To use this:
sampleCollection.GroupBy(
await BuildExceptKeySelector((CollectionType x) => new { x.N2, x.N5 })
).Select(....);
Borrowing from this answer here:
Create a class EqualityComparer
public class EqualityComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
IDictionary<string, object> xP = x as IDictionary<string, object>;
IDictionary<string, object> yP = y as IDictionary<string, object>;
if (xP.Count != yP.Count)
return false;
if (xP.Keys.Except(yP.Keys).Any())
return false;
if (yP.Keys.Except(xP.Keys).Any())
return false;
foreach (var pair in xP)
if (pair.Value.Equals( yP[pair.Key])==false)
return false;
return true;
}
public int GetHashCode(T obj)
{
return obj.ToString().GetHashCode();
}
}
Then create your GroupContent method:
private void GroupContent<T>(List<T> dataList, string[] columns, string[] columnsToExclude)
{
string[] columnsToGroup = columns.Except(columnsToExclude).ToArray();
EqualityComparer<IDictionary<string, object>> equalityComparer = new EqualityComparer<IDictionary<string, object>>();
var groupedList = dataList.GroupBy(x =>
{
var groupByColumns = new System.Dynamic.ExpandoObject();
((IDictionary<string, object>)groupByColumns).Clear();
foreach (string column in columnsToGroup)
((IDictionary<string, object>)groupByColumns).Add(column, GetPropertyValue(x, column));
return groupByColumns;
}, equalityComparer);
foreach (var item in groupedList)
{
Console.WriteLine("Group : " + string.Join(",", item.Key));
foreach (object obj in item)
Console.WriteLine("Item : " + obj);
Console.WriteLine();
}
}
private static object GetPropertyValue(object obj, string propertyName)
{
return obj.GetType().GetProperty(propertyName).GetValue(obj, null);
}
I extended the code above borrowing another answer.
public static class IEnumerableExt {
public static IEnumerable<T> GroupBye<T, C>(this IEnumerable<T> query, Func<IGrouping<IDictionary<string, object>, T>, C> grouping) where T : class
{
var cProps = typeof(C).GetProperties().Select(prop => prop.Name).ToArray();
var columnsToGroup = typeof(T).GetProperties().Select(prop => prop.Name).Except(cProps).ToArray();
var equalityComparer = new EqualityComparer<IDictionary<string, object>>();
return query
.GroupBy(x => ExpandoGroupBy(x, columnsToGroup), equalityComparer)
.Select(x => MergeIntoNew(x, grouping, cProps));
}
private static IDictionary<string, object> ExpandoGroupBy<T>(T x, string[] columnsToGroup) where T : class
{
var groupByColumns = new System.Dynamic.ExpandoObject() as IDictionary<string, object>;
groupByColumns.Clear();
foreach (string column in columnsToGroup)
groupByColumns.Add(column, typeof(T).GetProperty(column).GetValue(x, null));
return groupByColumns;
}
private static T MergeIntoNew<T, C>(IGrouping<IDictionary<string, object>, T> x, Func<IGrouping<IDictionary<string, object>, T>, C> grouping, string[] cProps) where T : class
{
var tCtor = typeof(T).GetConstructors().Single();
var tCtorParams = tCtor.GetParameters().Select(param => param.Name).ToArray();
//Calling grouping lambda function
var grouped = grouping(x);
var paramsValues = tCtorParams.Select(p => cProps.Contains(p) ? typeof(C).GetProperty(p).GetValue(grouped, null) : x.Key[p]).ToArray();
return (T)tCtor.Invoke(paramsValues);
}
private class EqualityComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
var xDict = x as IDictionary<string, object>;
var yDict = y as IDictionary<string, object>;
if (xDict.Count != yDict.Count)
return false;
if (xDict.Keys.Except(yDict.Keys).Any())
return false;
if (yDict.Keys.Except(xDict.Keys).Any())
return false;
foreach (var pair in xDict)
if (pair.Value == null && yDict[pair.Key] == null)
continue;
else if (pair.Value == null || !pair.Value.Equals(yDict[pair.Key]))
return false;
return true;
}
public int GetHashCode(T obj)
{
return obj.ToString().GetHashCode();
}
}
}
Which can be used in the following way:
var list = enumerable.GroupBye(grp => new
{
Value = grp.Sum(val => val.Value)
});
The result will like grouping all other columns but Value, which will be valued to the sum of grouped elements' value

Order a List without hardcoding the fields or direction

I have an ObservableCollection which I want to sort, not in place but I want to create a new sorted copy.
There's lots of examples on how to sort lists using nifty lambda expressions or using LINQ, but I can't hardcode the fields I want to sort by into code.
I have an array of NSSortDescription which work kinda like SortDescription. There's a string with the name of the property but the direction is specified by a bool (true = ascending). The first value in the array should be the primary sorting field, when the values in that field match, the second sort descriptor should be used, etc.
Example:
Artist: Bob Marley, Title: No Woman No Cry
Artist: Bob Marley, Title: Could You Be Loved
Artist: Infected Mushroom, Title: Converting Vegetarians
Artist: Bob Marley, Title: One Love
Artist: Chemical Brothers, Title: Do It Again
Sort descriptor: Artist descending, Title ascending.
Result:
Artist: Infected Mushroom, Title: Converting Vegetarians
Artist: Chemical Brothers, Title: Do It Again
Artist: Bob Marley, Title: Could You Be Loved
Artist: Bob Marley, Title: No Woman No Cry
Artist: Bob Marley, Title: One Love
Any suggestions on how to accomplish this?
UPDATE: Change Sort to OrderBy as Sort is unstable sort algorithm
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel;
namespace PNS
{
public class SortableList<T> : List<T>
{
private string _propertyName;
private bool _ascending;
public void Sort(string propertyName, bool ascending)
{
//Flip the properties if the parameters are the same
if (_propertyName == propertyName && _ascending == ascending)
{
_ascending = !ascending;
}
//Else, new properties are set with the new values
else
{
_propertyName = propertyName;
_ascending = ascending;
}
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
PropertyDescriptor propertyDesc = properties.Find(propertyName, true);
// Apply and set the sort, if items to sort
PropertyComparer<T> pc = new PropertyComparer<T>(propertyDesc, (_ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending);
//this.Sort(pc); UNSTABLE SORT ALGORITHM
this.OrderBy(t=>t, pc);
}
}
public class PropertyComparer<T> : System.Collections.Generic.IComparer<T>
{
// The following code contains code implemented by Rockford Lhotka:
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnadvnet/html/vbnet01272004.asp
private PropertyDescriptor _property;
private ListSortDirection _direction;
public PropertyComparer(PropertyDescriptor property, ListSortDirection direction)
{
_property = property;
_direction = direction;
}
public int Compare(T xWord, T yWord)
{
// Get property values
object xValue = GetPropertyValue(xWord, _property.Name);
object yValue = GetPropertyValue(yWord, _property.Name);
// Determine sort order
if (_direction == ListSortDirection.Ascending)
{
return CompareAscending(xValue, yValue);
}
else
{
return CompareDescending(xValue, yValue);
}
}
public bool Equals(T xWord, T yWord)
{
return xWord.Equals(yWord);
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
// Compare two property values of any type
private int CompareAscending(object xValue, object yValue)
{
int result;
if (xValue == null && yValue != null) return -1;
if (yValue == null && xValue != null) return 1;
if (xValue == null && yValue == null) return 0;
// If values implement IComparer
if (xValue is IComparable)
{
result = ((IComparable)xValue).CompareTo(yValue);
}
// If values don't implement IComparer but are equivalent
else if (xValue.Equals(yValue))
{
result = 0;
}
// Values don't implement IComparer and are not equivalent, so compare as string values
else result = xValue.ToString().CompareTo(yValue.ToString());
// Return result
return result;
}
private int CompareDescending(object xValue, object yValue)
{
// Return result adjusted for ascending or descending sort order ie
// multiplied by 1 for ascending or -1 for descending
return CompareAscending(xValue, yValue) * -1;
}
private object GetPropertyValue(T value, string property)
{
// Get property
PropertyInfo propertyInfo = value.GetType().GetProperty(property);
// Return value
return propertyInfo.GetValue(value, null);
}
}
}
You could dynamically create the OrderBy predicate based on string properties.
Func<MyType, object> firstSortFunc = null;
Func<MyType, object> secondSortFunc = null;
//these strings would be obtained from your NSSortDescription array
string firstProp = "firstPropertyToSortBy";
string secondProp = "secondPropertyToSortBy";
bool isAscending = true;
//create the predicate once you have the details
//GetProperty gets an object's property based on the string
firstSortFunc = x => x.GetType().GetProperty(firstProp).GetValue(x);
secondSortFunc = x => x.GetType().GetProperty(secondProp).GetValue(x);
List<MyType> ordered = new List<MyType>();
if(isAscending)
ordered = unordered.OrderBy(firstSortFunc).ThenBy(secondSortFunc).ToList();
else
ordered = unordered.OrderByDescending(firstSortFunc).ThenBy(secondSortFunc).ToList();
You could ceate a class named e.g. DynamicProperty which does retrieve the requested value. I do assume that the returned values do implement IComparable which should not be a too harsh limitation since you do want to compare the values anyway.
using System;
using System.Linq;
using System.Reflection;
namespace DynamicSort
{
class DynamicProperty<T>
{
PropertyInfo SortableProperty;
public DynamicProperty(string propName)
{
SortableProperty = typeof(T).GetProperty(propName);
}
public IComparable GetPropertyValue(T obj)
{
return (IComparable)SortableProperty.GetValue(obj);
}
}
class Program
{
class SomeData
{
public int X { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
SomeData[] data = new SomeData[]
{
new SomeData { Name = "ZZZZ", X = -1 },
new SomeData { Name = "AAAA", X = 5 },
new SomeData { Name = "BBBB", X = 5 },
new SomeData { Name = "CCCC", X = 5 }
};
var prop1 = new DynamicProperty<SomeData>("X");
var prop2 = new DynamicProperty<SomeData>("Name");
var sorted = data.OrderBy(x=> prop1.GetPropertyValue(x))
.ThenByDescending( x => prop2.GetPropertyValue(x));
foreach(var res in sorted)
{
Console.WriteLine("{0} X: {1}", res.Name, res.X);
}
}
}
}
I once wrote the following extension methods, which basically have the effect of either OrderBy or ThenBy, depending on whether the source is already ordered:
public static class Extensions {
public static IOrderedEnumerable<TSource> OrderByPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending) {
var orderedSource = source as IOrderedEnumerable<TSource>;
if (orderedSource != null) {
return orderedSource.CreateOrderedEnumerable(keySelector, comparer, descending);
}
if (descending) {
return source.OrderByDescending(keySelector, comparer);
}
return source.OrderBy(keySelector, comparer);
}
public static IOrderedEnumerable<TSource> OrderByPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) {
return source.OrderByPreserve(keySelector, null, false);
}
public static IOrderedEnumerable<TSource> OrderByPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) {
return source.OrderByPreserve(keySelector, comparer, false);
}
public static IOrderedEnumerable<TSource> OrderByDescendingPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) {
return source.OrderByPreserve(keySelector, null, true);
}
public static IOrderedEnumerable<TSource> OrderByDescendingPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) {
return source.OrderByPreserve(keySelector, comparer, true);
}
}
The interface is the same as OrderBy / OrderByDescending (alternatively you can pass descending as a boolean). You can write:
list.OrderByPreserve(x => x.A).OrderByPreserve(x => x.B)
which has the same effect as:
list.OrderBy(x => x.A).ThenBy(x => x.B)
Thus you could easily use keyboardP's solution with an arbitrary list of property names:
public static IEnumerable<TSource> OrderByProperties<TSource>(IEnumerable<TSource> source, IEnumerable<string> propertyNames) {
IEnumerable<TSource> result = source;
foreach (var propertyName in propertyNames) {
var localPropertyName = propertyName;
result = result.OrderByPreserve(x => x.GetType().GetProperty(localPropertyName).GetValue(x, null));
}
return result;
}
(the localPropertyName variable is used here because the iteration variable will have changed by the time the query is executed -- see this question for details)
A possible issue with this is that the reflection operations will be executed for each item. It may be better to build a LINQ expression for each property beforehand so they can be called efficiently (this code requires the System.Linq.Expressions namespace):
public static IEnumerable<TSource> OrderByProperties<TSource>(IEnumerable<TSource> source, IEnumerable<string> propertyNames) {
IEnumerable<TSource> result = source;
var sourceType = typeof(TSource);
foreach (var propertyName in propertyNames) {
var parameterExpression = Expression.Parameter(sourceType, "x");
var propertyExpression = Expression.Property(parameterExpression, propertyName);
var castExpression = Expression.Convert(propertyExpression, typeof(object));
var lambdaExpression = Expression.Lambda<Func<TSource, object>>(castExpression, new[] { parameterExpression });
var keySelector = lambdaExpression.Compile();
result = result.OrderByPreserve(keySelector);
}
return result;
}
Essentially what those Expression lines are doing is building the expression x => (object)x.A (where "A" is the current property name), which is then used as the ordering key selector.
Example usage would be:
var propertyNames = new List<string>() { "Title", "Artist" };
var sortedList = OrderByProperties(list, propertyNames).ToList();
You just need to add the ascending / descending logic.

how to select common elements according to the value of a property?

If I want the common elements in two list, I can use the intersect function:
var listC = listA.Intersect(listB);
But this compare objects. If the lists have objects of type Persons and I would like to get the persons with the same name for example, how could I do that? Where I set the condition of the name property?
Thanks.
Pass it a custom IEqualityComparer<T>.
First, make a class that implements that interface:
public class PersonNameEqualityComparer:IEqualityComparer<Person>
{
public int GetHashCode (Person obj)
{
return obj.Name.GetHashcode ();
}
public bool Equals (Person x, Person y)
{
return x.Name == y.Name;
}
}
Then, all you need to do is pass an instance of that IEqualityComparer to the intersect method.
var result = listA.Intersect(listB, new PersonNameEqualityComparer());
You could extend this to any object and any property, using generics and lambdas:
public class PropertyEqualityComparer<TObject, TProperty> : IEqualityComparer<TObject>
{
Func<TObject, TProperty> _selector;
IEqualityComparer<TProperty> _internalComparer;
public PropertyEqualityComparer(Func<TObject, TProperty> propertySelector, IEqualityComparer<TProperty> innerEqualityComparer = null)
{
_selector = propertySelector;
_internalComparer = innerEqualityComparer;
}
public int GetHashCode(TObject obj)
{
return _selector(obj).GetHashCode();
}
public bool Equals(TObject x, TObject y)
{
IEqualityComparer<TProperty> comparer = _internalComparer ?? EqualityComparer<TProperty>.Default;
return comparer.Equals(_selector(x), _selector(y));
}
}
You could then just use it like this:
var result = listA.Intersect(listB, new PropertyEqualityComparer<Person, string>(p => p.Name));
or like this:
var result = listA.Intersect(listB, new PropertyEqualityComparer<Person, string>(p => p.Age));
and so on.

What is a pattern to enumerate two lists at the same time, without index?

I'm looking for a creative pattern to enumerate two IEnumerable<>'s synchronized.
If I was making something up and adding to the C# syntax I might write:
foreach(var firstItem, var secondItem in this.ListOne, this.ListTwo)
{
if (firstItem.Prop == secondItem.Prop)
WorkSomeMagic(secondItem);
DoSomethingElse(firstItem);
}
Now, obviously that doesn't exist. What patterns have people used to accomplish something similar when dealing with enumerations that aren't accessible by index? Keep in mind, what is inside my pseudo-foreach would be more complex; I simplified for the example.
You're looking for Zip, which is new in .NET 4 or you can use the implementation here:
Is there a zip-like method in .Net?
I usually do the following:
using (IEnumerator<int> e1 = this.ListOne.GetEnumerator(),
e2 = this.ListTwo.GetEnumerator()) {
while (e1.MoveNext() && e2.MoveNext()) {
...
}
}
Or write an extension method:
public static void EnumerateWith<T>(this IEnumerable<T> left,
IEnumerable<T> right, Action<T,T> del) {
using (IEnumerator<T> l = left.GetEnumerator(),
r = right.GetEnumerator()) {
while (l.MoveNext() && r.MoveNext()) {
del(l.Current,r.Current);
}
}
}
ListOne.EnumerateWith(ListTwo, (left, right) => {
...
});
As with any general C# question, this will probably have 10 good answers posted before VS2008 even loads. Instead of that rat race, I'll come up with an offbeat "anti-pattern" you should never use. In fact, anyone writing mission critical code, please stop reading now.
using System;
using System.Collections.Generic;
using System.Linq;
class EnumTwoLists
{
static void Main(string[] args)
{
var left = new List<int>();
var right = new List<DateTime>();
var demo = new LinqAbuse<int, DateTime>(left, right);
demo.Populate(40, s => s * s, d => new DateTime(2009, d / 31 + 1, d % 31 + 1));
demo.Enumerate( (s, d) => Console.WriteLine(String.Format("Executing arbitrary code with {0} and {1}", s, d)) );
}
}
class LinqAbuse<T1, T2>
{
public LinqAbuse(List<T1> l, List<T2> r)
{
left = l;
right = r;
}
List<T1> left;
List<T2> right;
public void Populate(int size, Func<int, T1> leftGenerator, Func<int, T2> rightGenerator)
{
new int[size].Aggregate(0, (index, empty) => PopulateWrapper(left, right, leftGenerator, rightGenerator, index));
}
int PopulateWrapper(List<T1> left, List<T2> right, Func<int, T1> leftGenerator, Func<int, T2> rightGenerator, int index)
{
left.Add(leftGenerator(index));
right.Add(rightGenerator(index));
return ++index;
}
public void Enumerate(Action<T1, T2> loopBody)
{
left.Join(right, l => "", r => "",
(l, r) => ActionWrapper(l, r, loopBody),
new CartesianComparer<object>(right.Count))
.ToList();
}
object ActionWrapper(T1 x, T2 y, Action<T1, T2> action)
{
action(x, y);
return null;
}
}
class CartesianComparer<T> : IEqualityComparer<T>
{
public CartesianComparer(int _size)
{
size = _size;
equalsCounter = (size * (size - 1) >> 1) + size; // Combinations(size, 2) + (size - trueCounter)
}
private int size;
private int equalsCounter;
private int trueCounter = 0;
public bool Equals(T x, T y)
{
if (0 < --equalsCounter)
return false;
equalsCounter = size - ++trueCounter;
return true;
}
public int GetHashCode(T obj)
{
return 0;
}
}
Aww, isn't she cute? (alternate caption: Mommy, why is Anders crying?)
Ignoring checks for nulls and whatnot:
IEnumerable<T1> first;
IEnumerable<T2> second;
using (IEnumerator<T1> e1 = first.GetEnumerator()) {
using (IEnumerator<T2> e2 = second.GetEnumerator()) {
while (e1.MoveNext() && e2.MoveNext()) {
// do something eith e1.Current and e2.Current
}
}
}
I know this question is old but for anyone coming to this question now you can build on Jason's answer and JaredPar's answer with C#7's ValueTuple, which will give you a syntax similar to the original question. You may need to install the nuget package System.ValueTuple.
If you declare an extension method something like this:
internal static class EnumerableExtensions
{
internal static IEnumerable<(T1, T2)> EnumerateWith<T1, T2>(this IEnumerable<T1> first, IEnumerable<T2> second)
{
using (var firstEnumerator = first.GetEnumerator())
using (var secondEnumerator = second.GetEnumerator())
{
while(firstEnumerator.MoveNext() && secondEnumerator.MoveNext())
{
yield return (firstEnumerator.Current, secondEnumerator.Current);
}
}
}
}
Then you can use it like this:
List<Foo> foos = new List<Foo>()
{
new Foo(),
new Foo(),
new Foo()
};
List<Bar> bars = new List<Bar>()
{
new Bar(),
new Bar(),
new Bar()
};
foreach((Foo foo, Bar bar) in foos.EnumerateWith(bars))
{
Console.WriteLine(foo.ID);
Console.WriteLine(bar.ID);
}

Categories