I found this interesting article Reflection Performance - Create Delegate (Properties C#)
the described approach works great for properties. So I tried to to make it work for Methods, too, but without success.
Classes / Properties / Methods
public class bmecatContent
{
private bmecatHeader header;
private bmecatCatalog catalog;
private List<bmecatFieldValue> fieldValueList;
public bmecatContent()
{
header = new bmecatHeader();
catalog = new bmecatCatalog();
}
public string DeclarationVersion { get; set; }
public string DeclarationEncoding { get; set; }
public string BmecatVersion { get; set; }
public bmecatHeader Header
{ get { return header; } }
public bmecatCatalog Catalog
{ get { return catalog; } }
}
public class bmecatCatalog
{
private List<bmecatCatalogGroupSystem> catalogGroupSystem;
private List<bmecatClassificationSystem> classificationSystem;
private List<bmecatProduct> products;
private List<bmecatProductToCataloggroupMap> productToCataloggroupMap;
public bmecatCatalog()
{
catalogGroupSystem = new List<bmecatCatalogGroupSystem>();
classificationSystem = new List<bmecatClassificationSystem>();
products = new List<bmecatProduct>();
productToCataloggroupMap = new List<bmecatProductToCataloggroupMap>();
}
public List<bmecatClassificationSystem> Classification_System
{ get { return classificationSystem; } }
public List<bmecatCatalogGroupSystem> Catalog_Group_System
{ get { return catalogGroupSystem; } }
public List<bmecatProduct> Products
{ get { return products; } }
public List<bmecatProductToCataloggroupMap> Product_To_Cataloggroup_Map
{ get { return productToCataloggroupMap; } }
public bmecatProduct GetProductByInernationalPid(string Pid)
{
// linq
var query = from prodItem in products
from innerList in prodItem.Product_Details.International_PID
where innerList.PID == Pid
select prodItem;
return query.FirstOrDefault();
}
}
my current Approach looks like:
// Properties
public static Func<object, object> BuildGetAccessor(MethodInfo method)
{
var obj = Expression.Parameter(typeof(object), "o");
Expression<Func<object, object>> expr =
Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method),
typeof(object)),
obj);
return expr.Compile();
}
// Methods (with string Parameter)
public static Func<object, string, object> BuildMethodAccessor(MethodInfo method)
{
var obj = Expression.Parameter(typeof(object), "o");
var strParam = Expression.Parameter(typeof(string), "strParam");
//var param = method.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).FirstOrDefault();
var param = Expression.Convert(strParam, method.GetParameters().First().ParameterType);
Expression<Func<object, string, object>> expr =
Expression.Lambda<Func<object, string, object>>(
Expression.Convert(Expression.Call(Expression.Convert(obj, method.DeclaringType), method, param),
typeof(object)),
obj);
return expr.Compile();
}
this code generates messages, that for the lambda-declaration a wrong number of Parameters was used.
thx a lot for your help!
// Update
this is my "work in progress" part when it Comes to creating & using the delegates:
bmecatParser parser = new bmecatParser();
// parser contains Property BmecatContent of type bmecatContent
// BmecatContent contains all properties and Methods I Need to Access at runtime
// e.g. BmecatContent.Catalog, BmecatContent.Catalog.GetProductByInernationalPid(string Pid)
// gets instance of main-class
var property = parser.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Single(obj => obj.Name == "BmecatContent");
var access = Extensions.BuildGetAccessor(property.GetGetMethod());
var resultBmecatContent = access(parser);
// gets instance of class that holds method
property = resultBmecatContent.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Single(obj => obj.Name == "Catalog");
access = Extensions.BuildGetAccessor(property.GetGetMethod());
var resultCatalog = access(resultBmecatContent);
// here I try to get value from method that has 1 Parameter (string)
var method = resultCatalog.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).Single(obj => obj.Name == "GetProductByInernationalPid");
var accessProd = Extensions.BuildMethodAccessor(method);
var resultProduct = accessProd(resultCatalog, "4317784548366");
the idea behind this is to parse given classes + properties structure, where user provides propertynames / methodnames within mappinginstructions.
Related
Let's assume I have a class D that derives from DynamicObject. The intention is for D to wrap some object in a way to expose more properties than originally available in a wrapped object.
Let's also assume that the following expression compiles and executes and the variable value is 1 as expected.
var age = (int)((dynamic)new D(new Person{Age = 45})).Age;
Now, if I try to build an expression that uses D instead of Person directly like so:
var p = Expression.Parameter(typeof(D), "p");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, "p.Age > 45");
I get a nice ParseException telling me that "No property or field 'Age' exists in type 'D'". Substituting typeof(D) with typeof(Person) works.
How can I build an expression that treats p as dynamic? I have tried lots of things and I ended up using System.Linq.Dynamic from NuGet but still no luck.
EDIT: Here are the classes used.
EDIT: I have found a solution using DynamicExpression.CreateClass() but I am not too happy as it effectively duplicates my object which can sometimes have deep hierarchy. Also, this is highly inefficient if the expression is only touching one property.
public class D : DynamicObject
{
Person _p;
public D(Person p)
{
_p = p;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return new [] {"Age"};
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name;
if (name == "Age")
{
result = _p.Age;
return true;
}
result = null;
return false;
}
}
class Person
{
public int Age { get; set; }
}
Answer updated as original(stroke out) proved to be irrelevant.
I did another research and probably found solution to your problem. Your question lacking more context, but I guess it will work for you.
Solution
First thing to do to make dynamic behavior work is to implement IDynamicMetaObjectProvider interface in class(es). This interface represents dynamic object and has single method GetMetaObject which return DynamicMetaObject. In this case PersonMetaObject class.
public sealed class Person : IDynamicMetaObjectProvider {
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public int GetAge() {
return DateTime.Today.Year - BirthDate.Year;
}
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) {
return new PersonMetaObject(parameter, this);
}
}
Now you have to implement overrides to PersonMetaObject class to allow custom runtime behavior. (Read as fun part)
internal class PersonMetaObject : DynamicMetaObject {
private TypeInfo _typeInfo = typeof(Person).GetTypeInfo();
public PersonMetaObject(Expression parameter, Person value)
: base(parameter, BindingRestrictions.Empty, value) { }
public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {
Expression self = GetSelfExpression();
string propertyName = binder.Name;
return BindGetInternal(self, propertyName);
}
public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) {
Expression self = GetSelfExpression();
string propertyName = indexes.First().Value.ToString();
return BindGetInternal(self, propertyName);
}
private DynamicMetaObject BindGetInternal(Expression self, string propertyName) {
switch (propertyName) {
// Dynamic value computed in runtime
case "FullName": {
// Get FirstName property
Expression firstName = Expression.Property(self, nameof(Person.FirstName));
// Get LastName property
Expression lastName = Expression.Property(self, nameof(Person.LastName));
// Create constant containing space character
Expression space = Expression.Constant(" ");
Expression stringArray = Expression.NewArrayInit(typeof(string), firstName, lastName);
Expression fullName = Expression.Call(null, typeof(string).GetMethod(nameof(String.Join), new[] { typeof(string), typeof(string[]) }), space, stringArray);
// Create and return dynamic object metadata
return new DynamicMetaObject(Expression.Convert(fullName, typeof(object)), BindingRestrictions.GetTypeRestriction(Expression, LimitType));
}
// Defined method invoked
case "Age": {
// Get method info using type reflection
MethodInfo getAge = _typeInfo.GetMethod(nameof(Person.GetAge));
// Call reflected method
Expression age = Expression.Call(self, getAge);
// Create and return dynamic object metadata
return new DynamicMetaObject(Expression.Convert(age, typeof(object)), BindingRestrictions.GetTypeRestriction(Expression, LimitType));
}
//
default: {
// if none case was hit we need to make sure to return declared property if exists
if(_typeInfo.GetMembers().Any(m => m.Name == propertyName)) {
return new DynamicMetaObject(Expression.Convert(Expression.Property(self, propertyName), typeof(object)), BindingRestrictions.GetTypeRestriction(Expression, LimitType));
}
// If we get here something is wrong. To avoid exception we just return default value. But you would like to change it accordingly
// Create default object
Expression #default = Expression.Default(typeof(object));
// Create and return dynamic object metadata
return new DynamicMetaObject(#default, BindingRestrictions.GetTypeRestriction(Expression, LimitType));
}
}
}
private Expression GetSelfExpression() {
return Expression.Convert(Expression, LimitType);
}
}
Last part is to write some logic to test it is working. In code below you can see there are two filters for IEnumerable using Func and two IQueryable. IQueryable is using programatically created expression.
class Program {
static void Main(string[] args) {
// Array of concrete types assigned to dynamic enumerable
IEnumerable<dynamic> enumerable = new Person[] {
new Person { BirthDate = new DateTime(1975, 8, 14), FirstName = "Alan", LastName = "Smith" },
new Person { BirthDate = new DateTime(2006, 1, 26), FirstName = "Elisa", LastName = "Ridley" },
new Person { BirthDate = new DateTime(1993, 12, 1), FirstName = "Randy", LastName = "Knowles" },
new Person { BirthDate = new DateTime(1946, 5, 8), FirstName = "Melissa", LastName = "Fincher" }
};
IQueryable<dynamic> queryable = enumerable.AsQueryable();
// Helper method to write data to console
void Write(IEnumerable<dynamic> collection) {
foreach (var item in collection) {
Console.WriteLine($"{item.FullName} born {item.BirthDate.ToShortDateString()}");
}
Console.WriteLine();
}
// Property access on dynamic object
var youngsters = enumerable
.Where(m => m.Age < 21);
Console.WriteLine("Young people to test property acces:");
Write(youngsters);
// Index access on dynamic object
var adults = enumerable
.Where(m => m["Age"] > 20 && m["Age"] < 60);
Console.WriteLine("Adult people to test index access:");
Write(adults);
// Composed lambda expression for queryable (be aware it is not going to work with EF)
var seniors = queryable
.Where(ExpressionFactory.PropertyGreaterThanPredicate("Age", 59));
Console.WriteLine("Senior people to test property access lambda expression:");
Write(seniors);
// Composed lambda expression for queryable (be aware it is not going to work with EF)
var some = queryable
.Where(ExpressionFactory.IndexLessThanPredicate("Age", 60));
Console.WriteLine("Some people to test index access lambda expression:");
Write(some);
Console.ReadKey();
}
}
ExpressionFactory implmentation is returning expression that contains dynamic expression responsible for runtime invocation and possibility to reach dynamic behavior. That was probably the issue you have been experiencing during your tries.
public static class ExpressionFactory {
public static Expression<Func<dynamic, bool>> PropertyGreaterThanPredicate(string propertyName, int value){
var constant = Expression.Constant(value);
var parameter = Expression.Parameter(typeof(object), "m");
// Creating binder to access class member
var binder = Binder.GetMember(CSharpBinderFlags.None, propertyName, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
// Dynamic operation on the binder
var dynamic = Expression.Dynamic(binder, typeof(object), parameter);
// Converting result to same type as value to compare
var converted = Expression.Convert(dynamic, constant.Value.GetType());
var predicate = Expression.GreaterThan(converted, constant);
var lambda = Expression.Lambda<Func<dynamic, bool>>(predicate, parameter);
return lambda;
}
public static Expression<Func<dynamic, bool>> IndexLessThanPredicate(string indexName, int value) {
var constant = Expression.Constant(value);
var parameter = Expression.Parameter(typeof(object), "m");
// Creating binder to access indexer
var binder = Binder.GetIndex(CSharpBinderFlags.None, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
// Dynamic operation on the binder
var dynamic = Expression.Dynamic(binder, typeof(object), parameter, Expression.Constant(indexName));
// Converting result to same type as value to compare
var converted = Expression.Convert(dynamic, constant.Value.GetType());
var predicate = Expression.LessThan(converted, constant);
var lambda = Expression.Lambda<Func<dynamic, bool>>(predicate, parameter);
return lambda;
}
}
If you'll need full working sample you can find it in GitHub repository.
In case there will be still issues, let me know. I'll try to help.
Original answer
Only way how to make it work is to use generic method instead of passing parameter expression.
namespace ConsoleApp2 {
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Dynamic;
class Program {
static void Main(string[] args) {
var dynamics = new D[] {
new D(new Person { Age = 1 }),
new D(new Person { Age = 50 }),
new D(new Person { Age = 100 })
};
var first = dynamics[0].Age;
var second = dynamics.Where(x => x.Age > 45);
var filter = System.Linq.Dynamic.DynamicExpression.ParseLambda<D, bool>("Age > 45");
var third = dynamics.Where(filter.Compile());
}
public class D : DynamicObject {
Person _p;
public D(Person p) {
_p = p;
}
public Type Type => this._p.GetType();
public override IEnumerable<string> GetDynamicMemberNames() {
return new[] { "Age" };
}
public override bool TryGetMember(GetMemberBinder binder, out object result) {
var name = binder.Name;
if (name == "Age") {
result = _p.Age;
return true;
}
result = null;
return false;
}
public int Age => _p.Age;
}
public class Person {
public int Age { get; set; }
}
}
}
Hope it helps!
Check this fiddle for the error: https://dotnetfiddle.net/tlz4Qg
I have two classes like this:
public class ParentType{
private ParentType(){}
public int Id { get; protected set; }
public SubType Sub { get; protected set; }
}
public class SubType{
private SubType(){}
public int Id { get; protected set; }
}
I am going to transform a multilevel anonymous expression to a multilevel non-anonymous expression. To achieve this I have an expression like the below-mentioned one:
x => new
{
x.Id,
Sub = new
{
x.Sub.Id
}
}
To achieve that goal, I have transformed it to an expression like this:
x => new ParentType()
{
Id = x.Id,
Sub = new SubType()
{
Id = x.Sub.Id
},
}
But when I call Compile() method, I get the following error:
Variable 'x.Sub' of type 'SubType' referenced from scope '' but it is not defined
Here is my visitor class:
public class ReturnTypeVisitor<TIn, TOut> : ExpressionVisitor
{
private readonly Type funcToReplace;
private ParameterExpression currentParameter;
private ParameterExpression defaultParameter;
private Type currentType;
public ReturnTypeVisitor() => funcToReplace = typeof(Func<,>).MakeGenericType(typeof(TIn), typeof(object));
protected override Expression VisitNew(NewExpression node)
{
if (!node.Type.IsAnonymousType())
return base.VisitNew(node);
if (currentType == null)
currentType = typeof(TOut);
var ctor = currentType.GetPrivateConstructor();
if (ctor == null)
return base.VisitNew(node);
NewExpression expr = Expression.New(ctor);
IEnumerable<MemberBinding> bindings = node.Members.Select(x =>
{
var mi = currentType.GetProperty(x.Name);
//if the type is anonymous then I need to transform its body
if (((PropertyInfo)x).PropertyType.IsAnonymousType())
{
//This section is became unnecessary complex!
//
var property = (PropertyInfo)x;
var parentType = currentType;
var parentParameter = currentParameter;
currentType = currentType.GetProperty(property.Name).PropertyType;
currentParameter = Expression.Parameter(currentType, currentParameter.Name + "." + property.Name);
//I pass the inner anonymous expression to VisitNew and make the non-anonymous expression from it
var xOriginal = VisitNew(node.Arguments.FirstOrDefault(a => a.Type == property.PropertyType) as NewExpression);
currentType = parentType;
currentParameter = parentParameter;
return (MemberBinding)Expression.Bind(mi, xOriginal);
}
else//if type is not anonymous then simple find the property and make the memberbinding
{
var xOriginal = Expression.PropertyOrField(currentParameter, x.Name);
return (MemberBinding)Expression.Bind(mi, xOriginal);
}
});
return Expression.MemberInit(expr, bindings);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
if (typeof(T) != funcToReplace)
return base.VisitLambda(node);
defaultParameter = node.Parameters.First();
currentParameter = defaultParameter;
var body = Visit(node.Body);
return Expression.Lambda<Func<TIn, TOut>>(body, currentParameter);
}
}
And use it like this:
public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
var visitor = new ReturnTypeVisitor<Tin, Tout>();
var result = (Expression<Func<Tin, Tout>>)visitor.Visit(source);
return result;// result.Compile() throw the aforementioned error
}
Here is the extension methods used inside my Visitor class:
public static ConstructorInfo GetPrivateConstructor(this Type type) =>
type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
// this hack taken from https://stackoverflow.com/a/2483054/4685428
// and https://stackoverflow.com/a/1650895/4685428
public static bool IsAnonymousType(this Type type)
{
var markedWithAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), inherit: false).Any();
var typeName = type.Name;
return markedWithAttribute
&& (typeName.StartsWith("<>") || type.Name.StartsWith("VB$"))
&& typeName.Contains("AnonymousType");
}
Update
Here is the .Net Fiddle link for the problem: https://dotnetfiddle.net/tlz4Qg
Update
I have removed the extra codes that seems to be out of the problem scope.
The cause of the problem in question is the line
currentParameter = Expression.Parameter(currentType, currentParameter.Name + "." + property.Name);
inside VisitNew method.
With your sample, it creates a new parameter called "x.Sub", so if we mark the parameters with {}, the actual result is
Sub = new SubType()
{
Id = {x.Sub}.Id
},
rather than expected
Sub = new SubType()
{
Id = {x}.Sub.Id
},
In general you should not create new ParameterExpressions except when remapping lambda expressions. And all newly created parameters should be passed to Expression.Lambda call, otherwise they will be considered "not defined".
Also please note that the visitor code has some assumptions which doesn't hold in general. For instance
var xOriginal = Expression.PropertyOrField(currentParameter, x.Name);
won't work inside nested new, because there you need access to a member of the x parameter like x.Sub.Id rather than x.Id. Which is basically the corersonding expression from NewExpression.Arguments.
Processing nested lambda expressions or collection type members and LINQ methods with expression visitors requires much more state control. While converting simple nested anonymous new expression like in the sample does not even need a ExpressionVisitor, because it could easily be achieved with simple recursive method like this:
public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
return Expression.Lambda<Func<Tin, Tout>>(
Transform(source.Body, typeof(Tout)),
source.Parameters);
}
static Expression Transform(Expression source, Type type)
{
if (source.Type != type && source is NewExpression newExpr && newExpr.Members.Count > 0)
{
return Expression.MemberInit(Expression.New(type), newExpr.Members
.Select(m => type.GetProperty(m.Name))
.Zip(newExpr.Arguments, (m, e) => Expression.Bind(m, Transform(e, m.PropertyType))));
}
return source;
}
I'm trying to dynamically build the select statement of a Linq query.
I have a function like this:
public Task<List<T>> RunQuery<T>(
IQueryable<T> query,
FieldSelector<T> fields,
int pageNumber, int pageSize)
{
var skip = (pageNumber-1) * pageSize;
var query = query.Skip(skip).Take(pageSize);
var selector = fields.GetSelector();
return query.Select(selector).ToListAsync();
}
Here is the FieldSelector class: (I my code I have extra properties per field)
public class FieldSelector<T>
{
private List<LambdaExpression> expressions;
public FieldSelector()
{
expressions = new List<LambdaExpression>();
}
public void Add(Expression<Func<T, object>> expr)
{
expressions.Add(expr);
}
public Expression<Func<T, object>> GetSelector()
{
//Build an expression like e => new {e.Name, e.Street}
}
}
How to implement the GetSelector function? Is it possible? (without getting too complex) .
This is how I would like to use it:
var fields = new FieldSelector<Employee>();
fields.Add(e => e.Name);
fields.Add(e => e.Street);
RunQuery<Employee>(query, fields, 1, 100);
You need to generate a custom type how that is doing the compiler for Anonymous Type, because you cannot generate the really Anonymous Type with dynamic properties count. After generation this type you can easy set the assignments of expressions that you passed to FieldSelector and combine it to custom type.
public class FieldSelector<T>
{
private List<LambdaExpression> expressions;
public FieldSelector()
{
expressions = new List<LambdaExpression>();
}
public void Add(Expression<Func<T, object>> expr)
{
expressions.Add(expr);
}
public Expression<Func<T, object>> GetSelector()
{
// We will create a new type in runtime that looks like a AnonymousType
var str = $"<>f__AnonymousType0`{expressions.Count}";
// Create type builder
var assemblyName = Assembly.GetExecutingAssembly().GetName();
var modelBuilder = AppDomain.CurrentDomain
.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect)
.DefineDynamicModule("module");
var typeBuilder = modelBuilder.DefineType(str, TypeAttributes.Public | TypeAttributes.Class);
var types = new Type[expressions.Count];
var names = new List<string>[expressions.Count];
for (int i = 0; i < expressions.Count; i++)
{
// Retrive passed properties
var unExpr = expressions[i].Body as UnaryExpression;
var exp = unExpr == null ? expressions[i].Body as MemberExpression : unExpr.Operand as MemberExpression;
types[i] = exp.Type;
// Retrive a nested properties
names[i] = GetAllNestedMembersName(exp);
}
// Defined generic parameters for custom type
var genericParams = typeBuilder.DefineGenericParameters(types.Select((_, i) => $"PropType{i}").ToArray());
for (int i = 0; i < types.Length; i++)
{
typeBuilder.DefineField($"{string.Join("_", names[i])}", genericParams[i], FieldAttributes.Public);
}
// Create generic type by passed properties
var type = typeBuilder.CreateType();
var genericType = type.MakeGenericType(types);
ParameterExpression parameter = Expression.Parameter(typeof(T), "MyItem");
// Create nested properties
var assignments = genericType.GetFields().Select((prop, i) => Expression.Bind(prop, GetAllNestedMembers(parameter, names[i])));
return Expression.Lambda<Func<T, object>>(Expression.MemberInit(Expression.New(genericType.GetConstructors()[0]), assignments), parameter);
}
private Expression GetAllNestedMembers(Expression parameter, List<string> properties)
{
Expression expression = parameter;
for (int i = 0; i < properties.Count; ++i)
{
expression = Expression.Property(expression, properties[i]);
}
return expression;
}
private List<string> GetAllNestedMembersName(Expression arg)
{
var result = new List<string>();
var expression = arg as MemberExpression;
while (expression != null && expression.NodeType != ExpressionType.Parameter)
{
result.Insert(0, expression.Member.Name);
expression = expression.Expression as MemberExpression;
}
return result;
}
}
By the way, as you saw in the code above current solution doesn't work for cases when you try to retrieve property with more than 1 level nesting class1->class2->class3->Propery_1. It's not hard to fix it.
EDIT: Case above was fixed. Now you can retrive class1->class2->class3->Propery_1
And it's usage of it:
private class TestClass
{
public string Arg2 { get; set; }
public TestClass Nested { get; set; }
public int Id { get; set; }
}
var field = new FieldSelector<TestClass>();
field.Add(e => e.Arg2);
field.Add(e => e.Id);
field.Add(e => e.Nested.Id);
dynamic cusObj = field.GetSelector().Compile()(new TestClass { Arg2 = "asd", Id = 6, Nested = new TestClass { Id = 79 } });
Console.WriteLine(cusObj.Arg2);
Console.WriteLine(cusObj.Id);
Console.WriteLine(cusObj.Nested_Id);
Unfortunately, cusObj will be object at compile type and you cannot retrieve his propertis if you doesn't mark it as dynamic.
By the way, your public Task<List<T>> RunQuery<T> wouldn't be compile, because field.GetSelector() returns Func<T, object> and you will get a Task<List<object>> when you will invoke return return query.Select(field.GetSelector()).ToListAsync()
Hope, this is helpful.
I am trying to create a generic method that takes three parameters.
1) List collection
2) String PropertyName
3) String FilterString
The idea is we pass a collection of Objects, the name of the property of the object
and a Filter Criteria and it returns back a list of Objects where the property contains
the FilterString.
Also, the PropertyName is optional so if its not supplied I would like to return
all objects that contain the FilterString in any property.
Any pointers on this would be really helpful.
I am trying to have a method signature like this:
public static List FilterList(List collection, String FilterString, String Property = "")
This way I can call this method from anywhere and pass it any List and it would return me a filtered list.
You could do what you want using LINQ, as such,
var collection = ...
var filteredCollection =
collection.Where(item => item.Property == "something").ToList();
Otherwise, you could try Reflection,
public List<T> Filter<T>(
List<T> collection,
string property,
string filterValue)
{
var filteredCollection = new List<T>();
foreach (var item in collection)
{
// To check multiple properties use,
// item.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
var propertyInfo =
item.GetType()
.GetProperty(property, BindingFlags.Public | BindingFlags.Instance);
if (propertyInfo == null)
throw new NotSupportedException("property given does not exists");
var propertyValue = propertyInfo.GetValue(item, null);
if (propertyValue == filterValue)
filteredCollection.Add(item);
}
return filteredCollection;
}
The problem with this solution is that changes to the name of the property or misspellings result in a runtime error, rather than a compilation error as would be using an actual property expression where the name is hard-typed.
Also, do note that based on the binding flags, this will work only on public, non-static properties. You can modify such behavior by passing different flags.
You can use a combination of reflection and dynamic expressions for this. I've put together a sample that might look a bit long at the first look. However, it matches your requirements and addresses these by
Using reflection to find the properties that are of type string and match the property name - if provided.
Creating an expression that calls string.Contains on all properties that have been identified. If several properties have been identified, the calls to string.Contains are combined by Or-expressions. This filter expression is compiled and handed to the Where extension method as a parameter. The provided list is filtered using the expression.
Follow this link to run the sample.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;
public class Test
{
public static IEnumerable<T> SelectItems<T>(IEnumerable<T> items, string propName, string value)
{
IEnumerable<PropertyInfo> props;
if (!string.IsNullOrEmpty(propName))
props = new PropertyInfo[] { typeof(T).GetProperty(propName) };
else
props = typeof(T).GetProperties();
props = props.Where(x => x != null && x.PropertyType == typeof(string));
Expression lastExpr = null;
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "x");
ConstantExpression valueExpr = Expression.Constant(value);
foreach(var prop in props)
{
var propExpr = GetPropertyExpression(prop, paramExpr, valueExpr);
if (lastExpr == null)
lastExpr = propExpr;
else
lastExpr = Expression.MakeBinary(ExpressionType.Or, lastExpr, propExpr);
}
if (lastExpr == null)
return new T[] {};
var filterExpr = Expression.Lambda(lastExpr, paramExpr);
return items.Where<T>((Func<T, bool>) filterExpr.Compile());
}
private static Expression GetPropertyExpression(PropertyInfo prop, ParameterExpression paramExpr, ConstantExpression valueExpr)
{
var memberAcc = Expression.MakeMemberAccess(paramExpr, prop);
var containsMember = typeof(string).GetMethod("Contains");
return Expression.Call(memberAcc, containsMember, valueExpr);
}
class TestClass
{
public string SomeProp { get; set; }
public string SomeOtherProp { get; set; }
}
public static void Main()
{
var data = new TestClass[] {
new TestClass() { SomeProp = "AAA", SomeOtherProp = "BBB" },
new TestClass() { SomeProp = "BBB", SomeOtherProp = "CCC" },
new TestClass() { SomeProp = "CCC", SomeOtherProp = "AAA" },
};
var result = SelectItems(data, "", "A");
foreach(var item in result)
Console.WriteLine(item.SomeProp);
}
}
In contrast to a completely reflection based approach, this one assembles the filter expression only once and compiles it, so that I'd expect a (small) performance improvement.
You should use Dynamic LINQ, for example, given SomeClass:
public class SomeClass
{
public int SomeField { get; set; }
}
List<SomeClass> list = new List<SomeClass>() { new SomeClass() { SomeField = 2 } };
and then:
var temp = list.AsQueryable().Where("SomeField == 1").Select("it");
var result= temp .Cast<SomeClass>().ToList();
So your function would be even simpler, with property name and filter merged into one parameter:
public List<T> Filter<T>(List<T> list, string filter)
{
var temp = list.AsQueryable().Where(filter).Select("it");
return temp.Cast<T>().ToList();
}
and you can provide different filters, like for example "SomeField > 4 && SomeField < 10" etc.
When using Markus solution it'll only work when all String properties are not null.
To ensure you could do this:
class TestClass
{
private string _someProp { get; set; }
public string SomeProp {
get
{
if(string.IsNullOrEmpty(_someProp)
{
_someProp = "";
}
return _someProp;
}
set
{
_someProp = value;
}
}
private string _someOtherProp { get; set; }
public string SomeOtherProp {
get
{
if(string.IsNullOrEmpty(_someOtherProp)
{
_someOtherProp = "";
}
return _someOtherProp;
}
set
{
_someOtherProp = value;
}
}
}
Since my rep is less then 50 I cannot comment;)
I have the following class
public class MyClass
{
public bool Delete(Product product)
{
// some code.
}
}
Now I have a helper class that looks like this
public class Helper<T, TResult>
{
public Type Type;
public string Method;
public Type[] ArgTypes;
public object[] ArgValues;
public Helper(Expression<Func<T, TResult>> expression)
{
var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
this.Type = typeof(T);
this.Method = body.Method.Name;
this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray();
this.ArgValues = ???
}
}
The idea ist to use this code from somewhere:
// I am returning a helper somewhere
public Helper<T> GetMethod<T>()
{
var product = GetProduct(1);
return new Helper<MyClass>(x => x.Delete(product));
}
// some other class decides, when to execute the helper
// Invoker already exists and is responsible for executing the method
// that is the main reason I don't just comile and execute my Expression
public bool ExecuteMethod<T>(Helper<T> helper)
{
var instance = new MyClass();
var Invoker = new Invoker(helper.Type, helper.Method, helper.ArgTypes, helper.ArgValues);
return (bool)Invoker.Invoke(instance);
}
The point where I am stuck is how to extract the arguments from the expression itself.
I found this way
((ConstantExpression)((MemberExpression)body.Arguments[0]).Expression).Value
which seems to be an object type with a field "product" but I believe there must be a simpler solution.
Any suggestions.
Update
Just to clarify, I modified my code according to what I want to achive. In my real word application I already have a class that does the same but without an expression tree:
var helper = new Helper(typeof(MyClass), "Delete",
new Type[] { typeof(Product) }, new object[] {product}));
The main reason for my Helper<T> is to have Compile-Time checking if the method signature is valid.
Update 2
This is my current implementation, is there a better way to acces the values, without using reflection?
public Helper(Expression<Func<T, TResult>> expression)
{
var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
this.Type = typeof(T);
this.Method = body.Method.Name;
this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray();
var values = new List<object>();
foreach(var arg in body.Arguments)
{
values.Add(
(((ConstantExpression)exp.Expression).Value).GetType()
.GetField(exp.Member.Name)
.GetValue(((ConstantExpression)exp.Expression).Value);
);
}
this.ArgValues = values.ToArray();
}
This method works pretty well. It returns the argument types and values for an Expression>
private static KeyValuePair<Type, object>[] ResolveArgs<T>(Expression<Func<T, object>> expression)
{
var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
var values = new List<KeyValuePair<Type, object>>();
foreach (var argument in body.Arguments)
{
var exp = ResolveMemberExpression(argument);
var type = argument.Type;
var value = GetValue(exp);
values.Add(new KeyValuePair<Type, object>(type, value));
}
return values.ToArray();
}
public static MemberExpression ResolveMemberExpression(Expression expression)
{
if (expression is MemberExpression)
{
return (MemberExpression)expression;
}
else if (expression is UnaryExpression)
{
// if casting is involved, Expression is not x => x.FieldName but x => Convert(x.Fieldname)
return (MemberExpression)((UnaryExpression)expression).Operand;
}
else
{
throw new NotSupportedException(expression.ToString());
}
}
private static object GetValue(MemberExpression exp)
{
// expression is ConstantExpression or FieldExpression
if (exp.Expression is ConstantExpression)
{
return (((ConstantExpression)exp.Expression).Value)
.GetType()
.GetField(exp.Member.Name)
.GetValue(((ConstantExpression)exp.Expression).Value);
}
else if (exp.Expression is MemberExpression)
{
return GetValue((MemberExpression)exp.Expression);
}
else
{
throw new NotImplementedException();
}
}
You can compile the argument expression and then invoke it to calculate the value:
var values = new List<object>();
foreach(var arg in body.Arguments)
{
var value = Expression.Lambda(argument).Compile().DynamicInvoke();
values.Add(value);
}
this.ArgValues = values.ToArray();
Here is an example of creation of a delegate using a lambda. The object instance is encapsulated into the delegate using a C# feature called closure.
MyClass instance = new MyClass();
//This following line cannot be changed to var declaration
//since C# can't infer the type.
Func<Product, bool> deleteDelegate = p => instance.Delete(p);
Product product = new Product();
bool deleted = deleteDelegate(product);
Alternatively you are trying to create a Helper that automagically Currys.
public class Helper<T>
where T : new()
{
public TResult Execute<TResult>(Func<T, TResult> methodLambda)
{
var instance = new T();
return methodLamda(instance);
}
}
public void Main()
{
var helper = new Helper<MyClass>();
var product = new Product();
helper.Execute(x => x.Delete(product));
}
However I have to say this problem looks suspiciously like the creation of a Helper class to handle the lifetime of a WCF proxy....You know...just say...in which case this ISN'T how I would approach this...simply because this approach leaks WCF specific code into your domain.