Generic method with dynamically selected parameter in predicate - c#

I have many objects of diffrent type and i need to check few diffrent properties from each of them in the same way. I want to use this method inside object initializer like this:
collection.GroupBy(x => x.Identifier)
.Select(group => new SomeType
{
Identifier = group.Key,
Quantity = group.Sum(i => i.Quantity),
Type = MY_METHOD_HERE(group),
Name = MY_METHOD_HERE(group)
})
Strongly typed example for one of the properties:
private ItemType CheckItemTypeConsistency(IGrouping<string, Item> group)
{
if (group.Any(x => x.ItemType != group.First().ItemType))
{
throw new ArgumentException($"Item with number {group.Key} is inconsistent", nameof(Item.ItemType));
}
else
{
return group.First().ItemType;
}
}
But i also have other property in Item that needs to be checked in the same way with diffrent type, so i have similar method but .ItemType is changed everywhere to .Name and return type is string.
I also have diffrent object type that i need to use it for, so in another method Item is changed to Vehicle.
How to create generic method like that?
I tried something like this:
private TElement CheckConsistency<TKey, TElement>(IGrouping<TKey, TElement> group, (maybe something here?))
{
if (group.Any(x => x.(what here?) != group.First().(what here?)))
{
throw new ArgumentException($"Element with number {group.Key} is inconsistent");
}
else
{
return group.First();
}
}
I solved problem with returning value by returning whole item so i can just CheckConsistency().Property when invoking this method.
But i dont know what to do with (what here?).
I thought that maybe i can put something in (maybe something here?) that would be somehow used in place of (what here?)?
Any ideas? I'm not sure about reflection because this method could be called easily more than 1000 times depending on collection size and number of unique entries.
#Edit:
Lets say it's something like merging data from two files. For example 2 data sets of items where quantity is added together for items with the same identifier etc. but some properties should be the same like name and if they are diffrent then there's something wrong and i need to throw an error.
There are diffrent sets of data with completly diffrent properties like vehicles, but rules are similar, some fields are just added together etc, some must be identical.

Using an accessor function and genericizing the property type and object type, you have:
private TProp CheckConsistency<TClass, TProp>(IGrouping<string, TClass> group, Func<TClass, TProp> propFn) {
var firstPropValue = propFn(group.First());
if (group.Any(x => firstPropValue == null ? propFn(x) == null : !propFn(x).Equals(firstPropValue))) {
throw new ArgumentException($"Item with number {group.Key} is inconsistent");
}
else {
return firstPropValue;
}
}
Which you can use like:
var ans = collection.GroupBy(x => x.Identifier)
.Select(group => new SomeType {
Identifier = group.Key,
Quantity = group.Sum(i => i.Quantity),
Type = CheckConsistency(group, x => x.ItemType),
Name = CheckConsistency(group, x => x.Name)
});
If it is important to include the proper argument name in the exception, you could pass it in, take in an Expression<Func<>> and pull out the name, then compile the argument to a lambda to use (may be slow), or use reflection instead of a lambda property accessor (also possibly slow).
To use Reflection, I recommend caching the compiled function so you don't constantly re-compile each time the method is called:
// [Expression] => [Func]
Dictionary<LambdaExpression, Delegate> propFnCache = new Dictionary<LambdaExpression, Delegate>();
private TProp CheckConsistency<TClass, TProp>(IGrouping<string, TClass> group, Expression<Func<TClass, TProp>> propExpr) {
Func<TClass,TProp> propFn;
if (propFnCache.TryGetValue(propExpr, out var propDel))
propFn = (Func<TClass, TProp>)propDel;
else {
propFn = propExpr.Compile();
propFnCache.Add(propExpr, propFn);
}
var firstPropValue = propFn(group.First());
if (group.Any(x => !propFn(x).Equals(firstPropValue))) {
throw new ArgumentException($"Item with number {group.Key} is inconsistent", ((MemberExpression)propExpr.Body).Member.Name);
}
else {
return firstPropValue;
}
}

Related

How can I dynamically select properties for equivalency test - FluentAssertions

I'm creating unit tests in which I will be comparing lists of objects with one another.
Currently I am using Fluent assertions in combination with specflow and nunit. I already use the Fluent Assertions to make a comparison as following:
public void TestShizzle()
{
// I normally retrieve these lists from a moq database or a specflow table
var expected = list<myObject>
{
new myObject
{
A = 1,
B = "abc"
}
}
var found = list<myObject>
{
new myObject
{
A = 1,
B = "def"
}
}
// this comparison only compares a few columns. The comparison is also object dependent. I would like to make this dynamic
found.Should().BeEquivalentTo(
expected,
options =>
options.Including(x => x.A));
}
What I really want is to be able to use generics instead of a specified type. I also want to decide which properties to compare at compile time. This is because of the large number of tables in the database. I think i need to use Linq Expressions for this, but I don't know how to go about this. The function should look something like this:
public void GenericShizzle<T>(List<T> expected, List<T> found, IEnumerable<PropertyInfo> properties)
{
Expression<Func<T, object>> principal;
foreach(var property in properties)
{
// create the expression for including fields
}
found.Should().BeEquivalentTo(
expected,
options =>
// here is need to apply the expression.
}
I have no real idea how to get the correct expression for the job, or if this even the best method. I think I need to create an property expression that is understood by the include function, but maybe a different method can be used?
There is Including method overload accepting Expression<Func<IMemberInfo, bool>> which can be used to dynamically filter members based on information about them:
IEnumerable<PropertyInfo> properties = ...;
var names = properties
.Select(info => info.Name)
.ToHashSet();
found.Should()
.BeEquivalentTo(expected,
options => options.Including((IMemberInfo mi) => names.Contains(mi.Name))); // or just .Including(mi => names.Contains(mi.Name))

Can I use a List<T> and a List<Expression> to populate a wpf datagrid?

Can I use a List<T> and a List<Expression> to populate a wpf datagrid?
{
var processes = Process.GetProcesses().ToList();
PopulateDataGrid( processes, x => x.ProcessName, x => GetSafeFilename( x ) );
}
private string GetSafeFilename( Process p )
{
try
{
return p.MainModule.FileName;
}
catch ( Exception )
{
return "";
}
}
The idea is that I want to be able to pass a list and params list of expressions to populate a datagrid. I only want to show the list of expressions on the datagrid.
I'd also like to be able to get the underlying object for the selected row.
I know I can use anonymous types like:
var list = processes.Select( x => new {TagObject = x, ProcessName = x.ProcessName, Filename = GetSafeFilename( x )} ).ToList();
But then I have to make sure not to add "TagObject" to the datagrid.
Any ideas? I realy like the idea of the syntax:
PopulateDataGrid( processes, x => x.ProcessName, x => GetSafeFilename( x ) );
But I'm not sure how to make it happen.
You want to provide a set of expressions and use each expression to create its own colum in the grid. There is a major problem you'll have to resolve: DataGrid columns are resolved using data bindings to properties of the object:
<DataGridTextColumn Header="ProcessName" Binding="{Binding ProcessName}" />
so we can map a property-accessing expression to a DataGrid column with the appropriate binding. But your second column doesn't represent a property access, but rather a method call; and you can't set the binding of a datagridcolumn to a method call:
<!-- won't work -->
<DataGridTextColumn Header="GetSafeFilenamee" Binding="{Binding GetSafeFilename}" />
In this case, because the purpose of the method is to handle the possible exception on trying to access details on MainModule; we might be able to avoid the exception with a proprty access and using WPF's target fallback mechanism. But a general mechanism that would reach into the IL of any arbitrary method to figure out the relevant property access is almost certainly out of scope for what you want to do.
Instead of PopulateDataGrid taking multiple expressions, each with its own property access, I would suggest a single expression that contains multiple property accesses. I can think of two such expressions:
an expression that returns some array
PopulateDataGrid(processes, x => new [] { x.ProcessName, x.MainModule.FileName });
or an expression that returns an anonymous type. This has the added benefit of allowing you to pass headers to the columns:
PopulateDataGrid(processes, x => new { x.ProcessName, Path = x.MainModule.FileName });
Also, I would suggest exposing it as an extension method on DataGrid. The signature could look like this:
public static void PopulateDataGrid<TElement, TFieldsExpression>(this DataGrid dg, IEnumerable<TElement> itemsSource, Expression<Func<TElement, TFieldsExpression>> fieldsExpr) {
}
We need the TFieldsExpression generic parameter so the compiler can recognize the second parameter as an expression.
The first step is to parse the multi-expression into individual headers and property accesses. You could use something like the following:
private static List<(string name, Expression expr)> ParseFields<TElement, TFieldsExpression>(Expression<Func<TElement, TFieldsExpression>> fieldsExpression) {
var body = fieldsExpression.Body;
switch (body) {
// an array initialization with elements
// (as opposed to an array initialization with bounds -- new int[5])
case NewArrayExpression newArrayExpr when body.NodeType == ExpressionType.NewArrayInit:
return newArrayExpr.Expressions.Select(x => ("", x)).ToList();
// anonymous type
// the IsAnonymous extension method is included at the end of the post
case NewExpression newExpr when newExpr.Type.IsAnonymous():
return newExpr.Constructor.GetParameters().Select(x => x.Name).Zip(newExpr.Arguments).ToList();
default:
throw new ArgumentException("Unhandled expression type.");
}
}
Then you could write the following method:
public static void PopulateDataGrid<TElement, TFieldsExpression>(this DataGrid dg, IEnumerable<TElement> itemsSource, Expression<Func<TElement, TFieldsExpression>> fieldsExpr) {
dg.ItemsSource = itemsSource;
dg.Columns.Clear();
var fields = ParseFields(fieldsExpr);
foreach (var (name, expr) in fields) {
if (expr is MemberAccessExpression mexpr) {
dg.Columns.Add(new DataGridTextColumn {
Header = name,
Binding = new Binding(mexpr.Member.Name)
})
} else {
throw new ArgumentException("Unhandled expression type.");
}
}
}
You could then call this method as follows:
dg.PopulateDataGrid(list, x => new [] {x.ProcessName, x.HasExited, x.MachineName};
Note: most of this comes from sample code which accompanies an MSDN article I've written about expression trees. The sample code handles additional expression types, long path chains (e.g. x.MainModule.FileName) and method calls to String.Format.
Helper extensions:
// using System.Reflection;
public static bool IsAnonymous(this Type type) =>
type.HasAttribute<CompilerGeneratedAttribute>() && type.Name.Contains("Anonymous") && type.Name.ContainsAny("<>", "VB$");
public static bool HasAttribute<TAttribute>(this MemberInfo mi, bool inherit = false) where TAttribute : Attribute =>
mi.GetCustomAttributes(typeof(TAttribute), inherit).Any();
public static bool ContainsAny(this string s, params string[] testStrings) =>
testStrings.Any(x => s.Contains(x));

Cannot apply indexing with [] to an expression of type 'object' (even though the type is 'dynamic')

I have an ExpandoObject which is created like so:
public ExpandoObject Get()
{
var expando = new ExpandoObject();
var expandoDic = (IDictionary<string, dynamic>)expando;
// go through the items in the dictionary and copy over the key value pairs)
foreach (var f in GetFieldList())
{
if (f.MaxValues == 1)
{
var val = f.Values.Count > 0 ? f.Values[0] : null;
if (f.displayType == DisplayType.Entity && f.AttachedEntities != null && f.AttachedEntities.Any())
{
if (f.AttachedEntities.Count == 1)
{
expandoDic.Add(new KeyValuePair<string, dynamic>(f.Code, f.AttachedEntities[0].Get()));
}
else
{
expandoDic.Add(new KeyValuePair<string, dynamic>(f.Code, f.AttachedEntities.Select(e => e.Get())));
}
}
else
{
expandoDic.Add(new KeyValuePair<string, dynamic>(f.Code, GetTypedValue(f.GetValueType(), val)));
}
}
else
{
expandoDic.Add(new KeyValuePair<string, dynamic>(f.Code, (dynamic)f.Values.Select(v => GetTypedValue(f.GetValueType(), v))));
}
}
return expando;
}
The GetTypedValue simply converts the string value into the appropriate type and returns dynamic.
The problem I'm getting is that if I add a collection to expandoDic then I can't access the members without casting it to an ICollection type. Consider the following code, where myPage is an ExpandoObject created by the above method.
Response.Write(myPage.menu.items[0]);
The menu property is a dynamic object, as is items. The latter is a collection of strings, though the type is actually IEnumerable<dynamic>'. If I inspect myPage.menu.items, it tells me the type isdynamic {System.Linq.Enumerable.WhereSelectListIterator}`. The above code produces the following error:
Cannot apply indexing with [] to an expression of type 'object'
If I use First() instead of an index, I instead get this error:
'object' does not contain a definition for 'First'
I understand that I could cast items to IEnumerable and fix the problem right away, but I am writing a development framework and want to remove any barriers to developers using it.
For dynamic types you cannot use the extension method without conversion, but you can use static call of the method so instead of:
var a = yourDynamic.First();
you should write
var a = Enumerable.First(yourDynamic);
I figured out my issue. Where I was creating the IEnumerable of dynamics, I just needed to convert it to an array, like so:
expandoDic.Add(new KeyValuePair<string, dynamic>(f.Code, f.Values.Select(v => GetTypedValue(f.GetValueType(), v)).ToArray()));
This proves yet again that having to explain the problem thoroughly can often reveal the answer.

Storing and calling generically-typed delegates

What I'm looking for is probably not going to be possible without resorting to reflection. If that's the case, I'd still want to know the best way to pull it off.
Essentially, this is what I want my code to look like:
var instance = new MyClass();
instance.Add<int, string>(x => x.ToString());
instance.Add<string, Warehouse>(x => Warehouse.LookupByName(x));
instance.Add<Warehouse, IList<Supplier>>(x => x.Suppliers());
instance.Chain(3); // should call each lambda expression in turn
My question is, how can I store these delegates, each with a different signature, in a list in MyClass? And how can I call them later on when I want to, using the return value from each one as the input parameter to the next one?
The inside of MyClass may very well be a mess of List's and all that. But I'm not even sure where to start on this.
(Originally, I wanted to call new MyClass<int, string, Warehouse, IList<Supplier>>(). However, since there's no "type parameter array", I gave up on that approach.)
Well, you could store them all as Delegate - but the tricky thing is invoking them later.
If you're able to validate that the next delegate at any time is of the right type, e.g. by holding a Type reference for "the current output" you could always store a List<Func<object, object>> and make your Add method something like:
public void Add<TIn, TOut>(Func<TIn, TOut> func)
{
// TODO: Consider using IsAssignableFrom etc
if (currentOutputType != typeof(TIn))
{
throw new InvalidOperationException(...);
}
list.Add(o => (object) func((TIn) o));
currentOutputType = typeof(TOut);
}
Then to invoke them all:
object current = ...; // Wherever
foreach (var func in list)
{
current = func(current);
}
The Linq Select statement essentially does this...
var temp = instance.Select(x => x.ToString())
.Select(x => WareHouse.LookupByName(x))
.Select(x=> x.Suppliers());
List<List<Suppliers>> = temp.ToList(); //Evaluate statements
You can also store each intermediate Select call as an Enumerable to have the stated method you use in the OP.
class Program
{
static void Main(string[] args)
{
var instance = new MyClass();
instance.Add<int, string>(i => i.ToString());
instance.Add<string, int>(str => str.Length);
instance.Add<int, int>(i => i*i);
Console.WriteLine(instance.Chain(349));
Console.ReadLine();
}
}
public class MyClass
{
private IList<Delegate> _Delegates = new List<Delegate>();
public void Add<InputType, OutputType>(Func<InputType, OutputType> action)
{
_Delegates.Add(action);
}
public object Chain<InputType>(InputType startingArgument)
{
object currentInputArgument = startingArgument;
for (var i = 0; i < _Delegates.Count(); ++i)
{
var action = _Delegates[i];
currentInputArgument = action.DynamicInvoke(currentInputArgument);
}
return currentInputArgument;
}
}
If you want compile time type checking, what you are doing sounds suspiciously like plain old generic delegates. Assuming that there is some value to storing the individual functions that were Added (other than the Int to String conversion) and composing them later, you can do something like this:
var lookupWarehouseByNumber = new Func<int, Warehouse>(i => Warehouse.LookupByName(i.ToString()));
var getWarehouseSuppliers = new Func<Warehouse, IEnumerable<Supplier>>(w => w.Suppliers);
var getWarehouseSuppliersByNumber = new Func<int, IEnumerable<Supplier>>(i => getWarehouseSuppliers(lookupWarehouseByNumber(i)));

Using a Object property as method argument

I have a rather complex database, where we are using Linq to SQL. I'm creating a model layer where I would like to only have one method. But my problem is that we often like to order the collection. Is it possible somehow to accomplish something like this:
public static List<Object> GetObject(Object.Property)
{
return some Linq.ToList();
}
I know I can use linq on my list afterwards.
Hmm it was maybe a bit to diffuse question.
OK I solved it with reflection and a string in the argument..
MyObjectDataDataContext context = new MyObjectDataDataContext ();
PropertyInfo[] piArray = context.MyObject.GetType().GetProperties();
PropertyInfo pi = piArray.FirstOrDefault(s => s.Name == "property");
if (pi != null)
{
return context.MyObject.OrderBy(t => pi.PropertyType);
}
I guess you are trying to access the same data, but depending on a 'column' criteria, return the data sorted?
Once you have the IEnumerable data, you can sort it as follows:
list.OrderBy(t => t.ColumnYouWantToSortBy)
As in the following documentation

Categories

Resources