Passing/Specifty A Property In Generic Method? - c#

I am trying to move some code that I wrote to a more generic method. While the method is longer, the part I am having trouble with is the following :
public static void Test()
{
MyObjectType[] list1 = ListMyObjectTypeMethod1();
MyObjectType[] list2 = ListMyObjectTypeMethod2();
List<MyObjectType> linqAblelist1 = new List<MyObjectType>(list1);
List<MyObjectType> linqAblelist2 = new List<MyObjectType>(list2);
IEnumerable<MyObjectType> toBeAdded = linqAblelist1.Where(x => linqAblelist2.All(y => y.Property1 != x.Property1));
IEnumerable<MyObjectType> toBeDeleted = linqAblelist2.Where(a => linqAblelist1.All(b => b.Property1 != a.Property1));
}
And I am trying to pass in a generic type for MyObjectType, but where I have [How To Set Property Here?] how does one specify that in a parameter for the method?
public static void Test<T>(T[] x, T[] y)
{
List<T> list1 = new List<T>(x);
List<T> list2 = new List<T>(y);
IEnumerable<T> toBeAdded = list1.Where(x => list2.All(y => y.[How To Set Property Here?] != x.[How To Set Property Here?]));
IEnumerable<T> toBeDeleted = list2.Where(a => list1.All(b => b.[How To Set Property Here?])); != a.[How To Set Property Here?]));));
}

Pass in the selection of the property as a Func<T, TProperty>:
public static void Test<T, TProperty>(T[] x, T[] y, Func<T, TProperty> propertySelector)
{
List<T> list1 = new List<T>(x);
List<T> list2 = new List<T>(y);
IEnumerable<T> toBeAdded = list1.Where(x => list2.All(y => !propertySelector(y).Equals(propertySelector(x))));
IEnumerable<T> toBeDeleted = list2.Where(a => !list1.All(b => propertySelector(b).Equals(propertySelector(a))));
}
Then you can call it by specifying a lambda expression for propertySelector:
Test(someArray, someOtherArray, t => t.SomeProperty);

The best option is to introduce a generic type constraint that will make sure T either inherits from a specific class or implements an interface. In either case the class or interface have to declare Property1. E.g. like this:
public static void Test<T>(T[] x, T[] y) where T : IHasProperty1
{
…
}

You need to put some constraints on your generic type.
public static void Test<T>(T[] x, T[] y) where T : <SomeInterface>
{
List<T> list1 = new List<T>(x);
List<T> list2 = new List<T>(y);
IEnumerable<T> toBeAdded = list1.Where(x => list2.All(y => y.PropertyName != x.PropertyName));
IEnumerable<T> toBeDeleted = list2.Where(a => list1.All(b => b.PropertyName)); != a.PropertyName));));
}

You cann add a generic constraint that will ensure that T will have the properties you're expecting. Some thing like:
public static void Test<T>(T[] x, T[] y) where T : MyObjectType
{
List<T> list1 = new List<T>(x);
List<T> list2 = new List<T>(y);
IEnumerable<T> toBeAdded = list1.Where(x => list2.All(y => y.Property1 != x.Property1 ));
IEnumerable<T> toBeDeleted = list2.Where(a => list1.All(b => b.Property1 )); != a.[How To Set Property Here?]));));
}

Related

Pass expression to initializer

I would like to pass an expression that represents a variable to used when instantiating an object.
Instead of:
class MyObject : IMyInterface { ... }
var list = db.MyObjects.Where(x => !x.IsDeleted).ToList();
var anotherList = list.Select(x => new AnotherObject() {
Id = x.Id,
Value = x.Value
});
I would like to make this so that a list of objects of IMyInterface can be transformed into another type of list (AnotherObject as example) using defined expressions as so:
var list = db.MyObjects
.Where(x => !x.IsDeleted)
.ToAnotherObjectList(x => x.Id, x => x.Value);
...
public static List<AnotherObject> ToAnotherObjectList<T>(
this IEnumerable<IMyInterface> list,
Expression id,
Expression value)
{
return list.Select(x => new AnotherObject() { Id = id, Value = value }).ToList();
}
I'm not sure how to accomplish this. I know I can use reflection to create objects and set properties by a string but I'm not sure how to pass expressions.
UPDATE
Well, I thought I'd have to do some reflection but it's simpler than what I was thinking. Here's my solution that works in IRL.
public static IEnumerable<AnotherObject> ToAnotherObject<T>(this IEnumerable<T> list, Func<T, int> getId, Func<T, string> getValue, Func<T, bool> getSelected = null) where T : IMyInterface
{
return list.Select(x => new AnotherObject {
Display = getValue(x),
Id = getId(x),
Selected = getSelected != null && getSelected(x),
});
}
You could use a Func<TInput,TReturn> for that. For example:
public static List<AnotherObject> ToAnotherObjectList<T>(
this IEnumerable<T> list,
Func<T, int> getId,
Func<T, object> getValue)
{
return list.Select(x => new AnotherObject() { Id = getId(x), Value = getValue(x) }).ToList();
}
Call:
list.ToAnotherObjectList(i => i.Id, i=> i.Value);
In this example I used Funcs with one parameter (of type T) and return type int/object.

How to pass delegates as <T> parameter in function

public delegate bool CompareValue<in T1, in T2>(T1 val1, T2 val2);
public static bool CompareTwoLists<T1, T2>(IEnumerable<T1> list1, IEnumerable<T2> list2, CompareValue<T1, T2> compareValue)
{
return list1.Select(item1 => list2.Any(item2 => compareValue(item1, item2))).All(search => search)
&& list2.Select(item2 => list1.Any(item1 => compareValue(item1, item2))).All(search => search);
}
In the above function; how to pass "compareValue" as parameter while calling "CompareTwoLists" function?
With a lambda expression that matches the delegate:
var people = new List<Person>();
var orders = new List<Order>();
bool result = CompareTwoLists(people, orders,
(person, order) => person.Id == order.PersonId);
Or as a reference to a method that matches the delegate:
static bool PersonMatchesOrder(Person person, Order order)
{
return person.Id == order.PersonId;
}
bool result = CompareTwoLists(people, orders, PersonMatchesOrder);
You need to create a method (Normal or Anonymous) that matches that delegate's signature. Below is a sample:
var list1 = new List<string>();
var list2 = new List<int>();
CompareValue<string, int> compareValues = (x, y) => true;
CompareTwoLists(list1, list2, compareValues);
You can also replace the anonymous method, with a normal method:
CompareValue<string, int> compareValues = SomeComparingMethod;
static bool SomeComparingMethod(string str, int number)
{
// code here
}
Another Approach
You can change your method to use Func:
public static bool CompareTwoLists<T1, T2>(IEnumerable<T1> list1, IEnumerable<T2> list2,
Func<T1, T2, bool> compareValue)
{
return list1.All(x => list2.Any(y => compareValue(x, y)))
&& list2.All(x => list1.Any(y => compareValue(y, x)));
}
And Change the caller method to:
Func<User, Role, bool> compareValues =
(u, r) => r.Active
&& u.Something == r.Something
&& u.SomethingElse != r.SomethingElse);

Extending A Custom Sort With A Custom Comparer

I have a custom sort that I am using to sort a list which works fine
public static void Sort<T>(ref List<T> list, string propertyName, SortDirection direction)
{
var comparer = new CustomComparer();
list = direction == SortDirection.Ascending
? list.OrderBy(x => x.GetType().GetProperty(propertyName).GetValue(x, null)).ToList()
: list.OrderByDescending(x => x.GetType().GetProperty(propertyName).GetValue(x, null)).ToList();
}
now I'm trying to add a CustomComparer to the mix and I get an error when I extend the method.
The type arguments for method 'IOrderedEnumerable
System.Linq.Enumerable.OrderBy(this
IEnumerable, Func, IComparer)' cannot be
inferred from the usage. Try specifying the type arguments explicitly.
public static void Sort<T>(ref List<T> list, string propertyName, SortDirection direction)
{
list = direction == SortDirection.Ascending
? list.OrderBy(x => x.GetType().GetProperty(propertyName).GetValue(x, null), new CustomComparer()).ToList()
: list.OrderByDescending(x => x.GetType().GetProperty(propertyName).GetValue(x, null), new CustomComparer()).ToList();
}
I get that the OrderBy is not set correctly does anyone have any suggestions?
Thanks.
public class CustomComparer : IComparer<object>
{
public int Compare(object x, object y)
{
}
}
Specify type arguments explicitly <T, object> in OrderByDescending method.
public class MyComparer : IComparer<object>
{
public int Compare(object x, object y)
{
throw new NotImplementedException();
}
}
public static void Sort<T>(ref List<T> list, string propertyName)
{
list = list.OrderByDescending<T, object>(x => x.GetType().GetProperty(propertyName).GetValue(x, null), new MyComparer()).ToList();
}

Generic method to compare 2 strongly typed list

I have the following method that compares 2 list (of the same type) and returns the differences. How do I make this method accept lists of any type?
var differences = list1.Where(x => list2.All(x1 => x1.Name != x.Name))
.Union(list2.Where(x => list1.All(x1 => x1.Name != x.Name)));
To get the difference between two sets (with order independency and multiplicity independency), you can use: HashSet<T>.SymmetricExceptWith(IEnumerable<T>).
public static IEnumerable<T> GetSymmetricDifference<T>(IEnumerable<T> list1, IEnumerable<T> list2, IEqualityComparer<T> comparer = null)
{
HashSet<T> result = new HashSet<T>(list1, comparer);
result.SymmetricExceptWith(list2);
return result;
}
In your case, to use it:
var difference = GetSymmetricDifference(list1, list2, new MyComparer());
With a custom comparer:
public class MyComparer : IEqualityComparer<MyType>
{
public bool Equals(MyType x, MyType y)
{
return x.Name.Equals(y.Name);
}
public int GetHashCode(MyType obj)
{
return obj.Name == null ? 0 : obj.Name.GetHashCode();
}
}
What about this:
var differences = list1.Except(list2).Union(list2.Except(list1));

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.

Categories