I need to call a local generic method by reflection, but I can't figure out how to do it.
My core problem is that I'm using reflection to populate an POCO object from a set of queries reading EAV data from a MySQL database. I'm happy to show this code, but it's lengthy and complex. I've created a simplified example that I think summarizes the problem nicely.
Consider this code:
void Main()
{
var repository = new Repository();
repository.Store<Foo>(xs => xs.Count());
int Compute<M>(string[] source) => repository.Fetch<M>().Invoke(source);
Console.WriteLine(Compute<Foo>(new[] { "A", "B" }));
}
public class Foo { }
public class Repository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store<T>(Func<string[], int> value)
{
_store[typeof(T)] = value;
}
public Func<string[], int> Fetch<T>()
{
return (Func<string[], int>)_store[typeof(T)];
}
}
I have a Repository type that can store a Func<string[], int> indexed by Type.
In my Main method I have instantiated an instance of Repository and stored xs => xs.Count() against the type Foo.
I have a local generic method Compute that I can call easily when I know the type of M at compile time. The call Compute<Foo>(new[] { "A", "B" }) computes 2. That's no problem.
The issue occurs when I only know M at run-time.
Let's say I have this type:
public class Bar
{
public Foo Value;
}
Now my code in Main looks like this:
void Main()
{
var repository = new Repository();
repository.Store<Foo>(xs => xs.Count());
int Compute<M>(string[] source) => repository.Fetch<M>().Invoke(source);
var runtimeType = typeof(Bar).GetField("Value").FieldType;
var reflectedMethod = /* What Goes Here ??? */
Console.WriteLine(reflectedMethod.Invoke(new[] { "A", "B" }));
}
I am thoroughly stumped with the /* What Goes Here ??? */ part.
I found a similar question but that method doesn't deal with a local generic method.
Can someone help with how to invoke such a method at run-time?
Here's the full working code. It runs in LINQPad with NuGet "System.Interactive" and a also Maybe monad library, with a reference to my MySQL database.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
internal class MagentoAttribute : Attribute
{
public string AttributeCode { get; private set; }
public MagentoAttribute(string code)
{
this.AttributeCode = code;
}
}
public static class Magento
{
public abstract class ProductBase
{
public uint EntityId { get; private set; }
public string Sku { get; private set; }
public decimal? Quantity { get; private set; }
}
public static T[] GetProducts<T>(this UserQuery #this) where T : ProductBase, new()
{
var setEntityId = typeof(ProductBase).GetProperty("EntityId", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Public);
var setSku = typeof(ProductBase).GetProperty("Sku", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Public);
var setQuantity = typeof(ProductBase).GetProperty("Quantity", BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Public);
var results =
(
from cpe in #this.catalog_product_entity
where cpe.sku != null && !cpe.sku.StartsWith("SHIP")
join csi in #this.cataloginventory_stock_item on cpe.entity_id equals csi.product_id
select new { cpe.entity_id, cpe.sku, csi.qty }
)
.ToArray()
.Select(x =>
{
var t = new T();
setEntityId.SetValue(t, x.entity_id);
setSku.SetValue(t, x.sku);
setQuantity.SetValue(t, x.qty);
return t;
})
.ToArray();
Func<string[], Dictionary<string, Func<uint, Maybe<string>>>> getStrings = attributeCodes =>
(
from eet in #this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in #this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in #this.catalog_product_entity_varchar on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.Concat(
from eet in #this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in #this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in #this.catalog_product_entity_text on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<string>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
Func<string[], Dictionary<string, Func<uint, Maybe<int?>>>> getIntegers = attributeCodes =>
(
from eet in #this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in #this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in #this.catalog_product_entity_int on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<int?>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
Func<string[], Dictionary<string, Func<uint, Maybe<DateTime?>>>> getDateTimes = attributeCodes =>
(
from eet in #this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in #this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in #this.catalog_product_entity_datetime on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<DateTime?>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
Func<string[], Dictionary<string, Func<uint, Maybe<decimal?>>>> getDecimals = attributeCodes =>
(
from eet in #this.eav_entity_type
where eet.entity_type_code == "catalog_product"
from ea in #this.eav_attribute
where ea.entity_type_id == eet.entity_type_id
where attributeCodes.Contains(ea.attribute_code)
join cpev in #this.catalog_product_entity_decimal on ea.attribute_id equals cpev.attribute_id
where cpev.store_id == 0
select new { ea.attribute_code, cpev.entity_id, cpev.value }
)
.ToArray()
.GroupBy(x => x.attribute_code, x => new { x.entity_id, x.value })
.Select(x => new
{
attribute_code = x.Key,
f = x.ToDictionary(y => y.entity_id, y => y.value.ToMaybe()).Map(k => Maybe<decimal?>.Nothing)
})
.ToDictionary(x => x.attribute_code, x => x.f);
var prerepo = new Prerepository();
prerepo.Store<Maybe<string>>(getStrings);
prerepo.Store<Maybe<int?>>(getIntegers);
prerepo.Store<Maybe<DateTime?>>(getDateTimes);
prerepo.Store<Maybe<decimal?>>(getDecimals);
var collapse = new Dictionary<Type, Delegate>()
{
{ typeof(Maybe<int?>), (Func<Maybe<int?>, Maybe<int?>>)(m => m) },
{ typeof(Maybe<int>), (Func<Maybe<int?>, Maybe<int>>)(m => (!m.HasValue || (m.HasValue && m.Value == null)) ? Maybe<int>.Nothing : m.Value.Value.ToMaybe()) },
{ typeof(int?), (Func<Maybe<int?>, int?>)(m => m.HasValue ? m.Value : (int?)null) },
{ typeof(int), (Func<Maybe<int?>, int>)(m => (m.HasValue && m.Value != null) ? m.Value.Value : default(int)) },
{ typeof(Maybe<decimal?>), (Func<Maybe<decimal?>, Maybe<decimal?>>)(m => m) },
{ typeof(Maybe<decimal>), (Func<Maybe<decimal?>, Maybe<decimal>>)(m => (!m.HasValue || (m.HasValue && m.Value == null)) ? Maybe<decimal>.Nothing : m.Value.Value.ToMaybe()) },
{ typeof(decimal?), (Func<Maybe<decimal?>, decimal?>)(m => m.HasValue ? m.Value : (decimal?)null) },
{ typeof(decimal), (Func<Maybe<decimal?>, decimal>)(m => (m.HasValue && m.Value != null) ? m.Value.Value : default(decimal)) },
{ typeof(Maybe<DateTime?>), (Func<Maybe<DateTime?>, Maybe<DateTime?>>)(m => m) },
{ typeof(Maybe<DateTime>), (Func<Maybe<DateTime?>, Maybe<DateTime>>)(m => (!m.HasValue || (m.HasValue && m.Value == null)) ? Maybe<DateTime>.Nothing : m.Value.Value.ToMaybe()) },
{ typeof(DateTime?), (Func<Maybe<DateTime?>, DateTime?>)(m => m.HasValue ? m.Value : (DateTime?)null) },
{ typeof(Maybe<string>), (Func<Maybe<string>, Maybe<string>>)(m => m) },
{ typeof(string), (Func<Maybe<string>, string>)(m => m.HasValue ? m.Value : default(string)) },
};
var attributes =
Enumerable
.Concat(
typeof(T)
.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(x => new
{
Field = x,
Attribute = x.GetCustomAttribute<MagentoAttribute>(),
})
.Where(x => x.Attribute != null)
.Select(x => new
{
Inject = (Action<object, object>)((o, v) => x.Field.SetValue(o, v)),
x.Attribute.AttributeCode,
AttributeType = x.Field.FieldType
}),
typeof(T)
.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(x => new
{
Property = x,
Attribute = x.GetCustomAttribute<MagentoAttribute>(),
})
.Where(x => x.Attribute != null)
.Select(x => new
{
Inject = (Action<object, object>)((o, v) => x.Property.SetValue(o, v)),
x.Attribute.AttributeCode,
AttributeType = x.Property.PropertyType
}))
.Where(x => collapse.ContainsKey(x.AttributeType))
.ToArray();
var postrepo = new Postrepository();
postrepo.Store<Maybe<int?>>(null);
void Fetch<M>(string[] attributeCodes) => postrepo.Store<M>(prerepo.Fetch<M>()(attributeCodes));
void InvokeHelper(Action<string[]> prototype, Type type, object data)
{
var method = prototype.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(type);
concreteMethod.Invoke(prototype.Target, new[] { data });
}
Type lift(Type t)
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Maybe<>))
{
t = t.GetGenericArguments()[0];
}
if (!t.IsGenericType)
{
if (t.IsValueType)
{
t = typeof(Nullable<>).MakeGenericType(t);
}
}
t = typeof(Maybe<>).MakeGenericType(t);
return t;
}
(
from a in attributes
let m = lift(a.AttributeType)
group a.AttributeCode by m
)
.ForEach(x => InvokeHelper(Fetch<object>, x.Key, x.Distinct().ToArray()));
void Inject<M, P>(string attributeCode, Action<object, object> inject)
{
var b = postrepo.Fetch<M>();
var d = b[attributeCode];
var fmp = (Func<M, P>)collapse[typeof(P)];
results
.ForEach(r =>
{
var m = d(r.EntityId);
inject(r, fmp(m));
});
}
void InvokeHelper2(Action<string, Action<object, object>> prototype, Type[] types, object[] data)
{
var method = prototype.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(types);
concreteMethod.Invoke(prototype.Target, data);
}
(
from a in attributes
let m = lift(a.AttributeType)
group new { a.AttributeType, a.Inject } by new { a.AttributeCode, m }
)
.ForEach(xs =>
{
xs
.ForEach(y =>
{
InvokeHelper2(Inject<object, object>, new[] { xs.Key.m, y.AttributeType }, new object[] { xs.Key.AttributeCode, y.Inject });
});
});
return results;
}
public class Prerepository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store<T>(Func<string[], Dictionary<string, Func<uint, T>>> value)
{
_store[typeof(T)] = value;
}
public Func<string[], Dictionary<string, Func<uint, T>>> Fetch<T>()
{
return (Func<string[], Dictionary<string, Func<uint, T>>>)_store[typeof(T)];
}
}
public class Postrepository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store<T>(Dictionary<string, Func<uint, T>> value)
{
_store[typeof(T)] = value;
}
public Dictionary<string, Func<uint, T>> Fetch<T>()
{
return (Dictionary<string, Func<uint, T>>)_store[typeof(T)];
}
}
}
Now I can write this code:
public class Product : Magento.ProductBase
{
[Magento("name")] public string Name { get; private set; }
[Magento("is_deleted")] private Maybe<int?> _is_deleted = Maybe<int?>.Nothing;
public bool IsDeleted { get => _is_deleted.HasValue ? _is_deleted.Value == 1 : false; }
[Magento("brand")] private Maybe<string> _brand { get; set; } = Maybe<string>.Nothing;
public string Brand { get => _brand.HasValue ? _brand.Value : "(missing)"; }
[Magento("cost")] public decimal Cost { get; private set; }
}
And then this:
var ps =
Magento
.GetProducts<Product>(this)
.Where(x => x.Cost == 0m)
.Where(x => !x.IsDeleted)
.Where(x => x.Quantity > 0m);
And I now have a strongly-typed way to read the Magento EAV data structure.
Jon Skeets answer will work for your problem too.
I only changed the signature to match your requirements:
var repository = new Repository();
repository.Store<Foo>(xs => xs.Count());
int Compute<M>(string[] source) => repository.Fetch<M>().Invoke(source);
// Call Compute<M>
var runtimeKnownTime = typeof(Foo);
var computeResult = InvokeHelper(Compute<object>, new[] { "A", "B" }, runtimeKnownTime);
Console.WriteLine(computeResult);
This uses the following implementation of InvokeHelper:
// This code was created by Jon Skeet : https://stackoverflow.com/a/43349127/2729609
// I only changed Action<int> to Func<int, int> and changed the return type.
private static int InvokeHelper(Func<string[], int> int32Func, object data, Type type)
{
// You probably want to validate that it really is a generic method...
var method = int32Func.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(type);
return (int)concreteMethod.Invoke(int32Func.Target, new[] { data });
}
DEMO
BUT I don't see the need to use this hack. You don't need the generic type in your case.
Change the repository to:
public class Repository
{
private Dictionary<Type, object> _store = new Dictionary<Type, object>();
public void Store(Type id, Func<string[], int> value)
{
_store[id] = value;
}
// optional
public void Store<T>(Func<string[], int> value) => this.Store(typeof(T), value);
public Func<string[], int> Fetch(Type id)
{
return (Func<string[], int>)_store[id];
}
// optional
public Func<string[], int> Fetch<T>() => this.Fetch(typeof(T));
}
And you can use it without generics:
var repository = new Repository();
repository.Store(typeof(Foo), xs => xs.Count());
int Compute(Type id, string[] source) => repository.Fetch(id).Invoke(source);
// Call Compute<M>
var runtimeKnownTime = typeof(Foo);
Console.WriteLine(Compute(runtimeKnownTime, new[] { "A", "B" }));
If you want, create generic overloads for the methods Fetch and Store that call the shown implementation using typeof(T). I markted this method with optional in my sample implemenation.
Related
I am using an expression tree as an alternative to automapper to map source properties to target properties using below code
What i am doing is, I have created static method inside static class for mapping and assigning inner child object property to outer object property
public static class PropertyMapper<TSource, TDest>
{
private static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> _mappingExpression;
private static Func<TSource, Dictionary<string, MasterSection>, TDest> _mapper;
static PropertyMapper()
{
_mappingExpression = ProjectionMap();
_mapper = _mappingExpression.Compile();
}
public static Func<TSource, Dictionary<string, MasterSection>, TDest> Mapper => _mapper;
public static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> ProjectionMap()
{
var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
var targetProperties= typeof(TDest).GetProperties().Where(p => p.CanWrite);
var propertyMap =
from d in targetProperties
join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
where d.Name != "SourceOfDataId" && d.Name!= "SourceOfData"
select new { Source = s, Dest = d };
var itemParam = Expression.Parameter(typeof(TSource), "item");
var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source))).ToList();
var sourceOfDataIdProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfDataId");
if (sourceOfDataIdProp != null)
{
memberBindings.Add(Expression.Bind(sourceOfDataIdProp,Expression.Convert(Expression.Property(Expression.Property(itemParam, "SourceOfData"),"Id"),typeof(Guid?))));
}
var sourceOfDataProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfData");
if(sourceOfDataProp != null)
{
// here i would like to update `sourceOfData` object property "isApproved"
}
var newExpression = Expression.New(typeof(TDest));
var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
var projection = Expression.Lambda<Func<TSource, Dictionary<string, MasterSection>, TDest>>(memberInitExpression, itemParam, dictParam);
return projection;
}
}
and i am using the above method here in below to map the source properties to target properties
AirflowsLab = sourceMechanicalData.AirflowsLab.Select(a => PropertyMapper<LibraryLabAirflow, LibraryLabAirflow>.Mapper(a, masterSectionMappedLibrary)).ToList();
and the structure for LibraryLabAirflow is looks like as below
public class LibraryLabAirflow
{
[ForeignKey("SourceOfData")]
public Guid? SourceOfDataId { get; set; }
public virtual CodeStandardGuideline SourceOfData { get; set; }
}
Above mapping is working fine, what i am trying is now that i need to access the sourceOfData child object of target and update the property for sourceOfData and map that updated child object to source child object sourceOfData.
below is the sourceOfData object details
"SourceOfData":{
"Id": "c5bf3585-50b1-4894-8fad-0ac884343935",
"IsApproved": null, // trying to set this to true instead of null inside target object
"MasterSection": null
},
I am not sure how to access the child object property using an expression tree in this scenario and i cannot use automapper library. Could any one please let me know how to access the child object property and update and assign back to target.
What i am trying to generate an expression looks like this source.SourceOfData = target.SourceOfData but before this i need to update one of the property of target.SourceOfData
Many thanks in advance
Desired expression :
AirflowsLab = sourceMechanicalData.AirflowsLab.Where(a => a != null).Select(item => new LibraryLabAirflow()
{
SourceOfData = new CodeStandardGuideline()
{
IsApproved = true,// trying to set this through expression tree
City = item.SourceOfData.City
......
.......
}
}).ToList(),
trying like this is not working as well, 1
var sourceOfDataProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfData");
if(sourceOfDataProp != null)
{
// here need to get the sourceofdata properties
var sourceOfDataProperty = Expression.Property(Expression.Constant(sourceOfDataProp), "IsApproved");
}
Update:
i have implemented the logic inside if block of sourceOfDataProp != null but getting an error
if (sourceOfDataProp != null)
{
var targetitemParam = Expression.Parameter(typeof(TTarget), "item");
var sourceOfDataPropertiesFilter = new List<string>()
{
"IsApproved"
};
var sourceItem = Expression.Property(itemParam, typeof(TSource).GetProperty("SourceOfData"));
var sourcePropertyInfo = sourceItem.Type.GetProperties().Where(p => p.CanRead);
var targetItem = Expression.Property(targetitemParam, typeof(TTarget).GetProperty("SourceOfData"));
var targetPropertyInfo = targetItem.Type.GetProperties().Where(p => p.CanWrite);
var sourceOfDataPropertyMap = from tp in targetPropertyInfo
join sp in sourcePropertyInfo
on new { tp.Name, tp.PropertyType } equals new { sp.Name, sp.PropertyType }
where !sourceOfDataPropertiesFilter.Contains(tp.Name)
select new { Source = sp, Target = tp };
// getting error at below line type of arguments does not match
var sourceOfDataMemberBindings = sourceOfDataPropertyMap.Select(p => Expression.Bind(p.Target, Expression.PropertyOrField(targetitemParam, "SourceOfData"))).ToList();
}
I have solved this problem like as below
if (sourceOfDataProp != null)
{
var targetItemParam = Expression.Parameter(typeof(TTarget), "item");
var sourceOfDataPropertiesFilter = new List<string>()
{
"IsApproved"
};
var sourceItem = Expression.Property(itemParam, typeof(TSource).GetProperty("SourceOfData"));
var sourceOfDataSourceProperties = sourceItem.Type.GetProperties().Where(p => p.CanRead);
var targetItem = Expression.Property(targetItemParam, typeof(TTarget).GetProperty("SourceOfData"));
var sourceOfDataTargetProperties = targetItem.Type.GetProperties().Where(p => p.CanWrite);
var sourceOfDataPropertyMap = sourceOfDataTargetProperties.Join(sourceOfDataSourceProperties,
t => new { t.Name, t.PropertyType },
s => new { s.Name, s.PropertyType },
(t, s) => new { Source = s, Target = t })
.Where(t => !sourceOfDataPropertiesFilter.Contains(t.Target.Name));
var sourceOfDataMemberBindings = sourceOfDataPropertyMap.Select(p => Expression.Bind(p.Target, Expression.Property(sourceItem, p.Source))).ToList();
var sourceOfDataIsApprovedProp = sourceOfDataTargetProperties.FirstOrDefault(s => s.Name == "IsApproved");
if (sourceOfDataIsApprovedProp != null)
{
sourceOfDataMemberBindings.Add(Expression.Bind(sourceOfDataIsApprovedProp, Expression.Constant(true, typeof(bool?))));
}
var sourceOfDataExpression = Expression.New(typeof(DesignHub.Entities.CodeStandardGuideline));
var sourceOfDataMemberInitExpression = Expression.MemberInit(sourceOfDataExpression, sourceOfDataMemberBindings);
memberBindings.Add(Expression.Bind(sourceOfDataProp, sourceOfDataMemberInitExpression));
}
I try to get runtime method infoes in static class. I have four static method inside the class and each name is equal, also parameter name is equal. The only difference is their types. One of four method has string parameter so it is easy to get method info. However the others don't working. I find several advice but that are not working.
All test code is here.
class Program {
static void Main(string[] args) {
//ok
var stringMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(string) });
//not working
var dictMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(Dictionary<,>) });
//not working
var genericMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(object) });
//not working
var listMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(List<>) });
//not working
var res = typeof(TestClass)
.GetRuntimeMethods()
.Where(x => x.Name.Equals("TestMethod"))
.Select(m => new { Method = m, Parameters = m.GetParameters() })
.FirstOrDefault(p =>
p.Parameters.Length == 1
&& p.Parameters[0].ParameterType.IsGenericType
&& p.Parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(ICollection<>)
);
}
}
public static class TestClass {
public static bool TestMethod(string item) {
return true;
}
public static bool TestMethod<TKey, TValue>(Dictionary<TKey, TValue> item) {
return true;
}
public static bool TestMethod<T>(T item) {
return true;
}
public static bool TestMethod<T>(List<T> item) {
return true;
}
}
If you are using .net core 2.1 or greater, you can use Type.MakeGenericMethodParameter to let you refer to a generic parameter of a method. You can use that to create a generic type argument that will work with GetMethod (not available for GetRuntimeMethod).
var stringMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(string) });
Type[] dictionaryTypeParameters = { typeof(Dictionary<,>).MakeGenericType(Type.MakeGenericMethodParameter(0), Type.MakeGenericMethodParameter(1)) };
MethodInfo dictMethodInfo = typeof(TestClass).GetMethod("TestMethod", 2, dictionaryTypeParameters);
MethodInfo listMethodInfo = typeof(TestClass).GetMethod("TestMethod", 1, new[] { typeof(List<>).MakeGenericType(Type.MakeGenericMethodParameter(0)) });
MethodInfo genericMethodInfo = typeof(TestClass).GetMethod("TestMethod", 1, new[] { Type.MakeGenericMethodParameter(0) });
Some interesting reading on the topic here.
Let's say we would like to use this method to get any MethodInfo for the various TestMethod. Note that they all have exactly one parameter, so p.Parameters.Length == 1 is useless:
Defined as bool TestMethod(string item). We can use
.FirstOrDefault(p => p.Method.IsGenericMethod)
Defined as bool TestMethod<TKey, TValue>(Dictionary<TKey, TValue> item)
.FirstOrDefault(p =>
p.Method.IsGenericMethod &&
p.Method.GetGenericArguments().Length == 2)
Defined as bool TestMethod<T>(T item)
.FirstOrDefault(p =>
p.Method.IsGenericMethod &&
p.Parameters[0].ParameterType == m.Method.GetGenericArguments()[0]
)
Defined as TestMethod<T>(List<T> item)
.FirstOrDefault(p =>
p.Method.IsGenericMethod &&
p.Parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(List<>)
)
In case of generic methods you have to query the MethodInfo object to get the appropriate method.
You can do it as below -
var dictMethodInfo = typeof(TestClass).GetMethods().Single(m => m.Name == "TestMethod" && m.IsGenericMethod &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.IsGenericType &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Dictionary<,>));
In your case, getting the MethodInfo for TestMethod<T> is bit tricky, but below should work -
var genericMethodInfo = typeof(TestClass).GetMethods().Single(m => m.Name == "TestMethod" && m.IsGenericMethod &&
!m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.IsGenericType);
Final Code -
var stringMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(string) });
var dictMethodInfo = typeof(TestClass).GetMethods().Single(m => m.Name == "TestMethod" && m.IsGenericMethod &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.IsGenericType &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Dictionary<,>));
var genericMethodInfo = typeof(TestClass).GetMethods().Single(m => m.Name == "TestMethod" && m.IsGenericMethod &&
!m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.IsGenericType);
var listMethodInfo = typeof(TestClass).GetMethods().Single(m => m.Name == "TestMethod" && m.IsGenericMethod &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.IsGenericType &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(List<>));
I am trying to verify a complex collection was passed into a method.
I can't seem to think of how to write the lambda to test against.
Here is a simplified version of what I am trying to do:
var parameters = new List<NotificationParameter>()
{
new NotificationParameter()
{
Key = "photoShootId",
Value = 1,
},
};
message.Verify(m => m.Load(It.Is<List<NotificationParameter>>(
p => // ??? Ensure that 'p' and 'parameters' have
// the same elements and values for 'Key' and 'Value'
)
));
Just passing in parameters to verify fails the test, so I would like to test against the Key and Value properties.
p above is of type List<NotificationParameter>.
You may use .All which execute an expression on each of the element and return true if all returns true, so for your particular case we could write the condition as
message.Verify(m => m.Load(It.Is<List<NotificationParameter>>(
pList => pList.All(p => parameters.Any(pTest => p.Key == pTest.Key && p.Value == pTest.Value))
)
));
Other method is to use IEqualityComparer & SequenceEqual
eg
class NotificationParameterComparer : IEqualityComparer<NotificationParameter>
{
public bool Equals(NotificationParameter x, NotificationParameter y)
{
if(x==null || y == null)
return false;
return x.Key == y.Key && x.Value == y.Value
}
public int GetHashCode(NotificationParameter parameter)
{
if (Object.ReferenceEquals(parameter, null)) return 0;
int hashKey = parameter.Key == null ? 0 : parameter.Key.GetHashCode();
int hashValue = parameter.Value.GetHashCode();
return hashKey ^ hashValue;
}
}
then use it as
message.Verify(m => m.Load(It.Is<List<NotificationParameter>>(
pList => pList.SequenceEqual(parameters, new NotificationParameterComparer())
)
));
To match the contents of a list one can use SequenceEquals() and an IEqualityComparer<>
public class NotificationParameter {
public NotificationParameter(string key, int value) {
Key = key;
Value = value;
}
public string Key { get; set; }
public int Value { get; set; }
}
public interface IService {
void Load(IEnumerable<NotificationParameter> parameters);
}
public class ClientClass {
private readonly IService _service;
public ClientClass(IService service) {
_service = service;
}
public void Run(IEnumerable<NotificationParameter> parameters) {
_service.Load(parameters);
}
}
public class NotificationComparer : IEqualityComparer<NotificationParameter> {
public bool Equals(NotificationParameter x, NotificationParameter y) {
return Equals(x.Key, y.Key)
&& x.Value.Equals(y.Value);
}
public int GetHashCode(NotificationParameter obj) {
return obj.Value.GetHashCode() ^ obj.Key.GetHashCode();
}
}
private readonly static NotificationComparer Comparer = new NotificationComparer();
[TestMethod]
public void VerifyLoadCompareValues() {
var parameters = new List<NotificationParameter> {
new NotificationParameter("A", 1),
new NotificationParameter("B", 2),
new NotificationParameter("C", 3),
};
var expected = new List<NotificationParameter> {
new NotificationParameter("A", 1),
new NotificationParameter("B", 2),
new NotificationParameter("C", 3),
};
var mockService = new Mock<IService>();
var client = new ClientClass(mockService.Object);
client.Run(parameters);
mockService.Verify(mk => mk.Load(It.Is<IEnumerable<NotificationParameter>>( it=> it.SequenceEqual(expected,Comparer))));
}
If the order will not be the same then a helper method to sort and then compare can be used.
[TestMethod]
public void VerifyLoadCompareDifferentOrder() {
var parameters = new List<NotificationParameter> {
new NotificationParameter("A", 1),
new NotificationParameter("B", 2),
new NotificationParameter("C", 3),
};
var expected = new List<NotificationParameter> {
new NotificationParameter("B", 2),
new NotificationParameter("C", 3),
new NotificationParameter("A", 1),
};
var mockService = new Mock<IService>();
var client = new ClientClass(mockService.Object);
client.Run(parameters);
mockService.Verify(mk => mk.Load(It.Is<IEnumerable<NotificationParameter>>(it => AreSame(expected, it))));
}
private static bool AreSame(
IEnumerable<NotificationParameter> expected,
IEnumerable<NotificationParameter> actual
) {
var ret = expected.OrderBy(e => e.Key).SequenceEqual(actual.OrderBy(a => a.Key), Comparer);
return ret;
}
I have list of multiple types. Somthing like
List<object> list = new List<object>() { 11.21, 323, 4, 3221, 221, 2, "asd" };
var qu = list.OrderBy((o) => o);
qu.ToList().ForEach((e) => Console.WriteLine(e)); // throws exception
Exception thrown is "Object must be of type Int32."
How can I write the Linq to sort such a list.
That's one of the most crazy LINQ queries I've ever written...
var qu = list.Select(x =>
{
decimal? d;
try
{
d = Convert.ToDecimal(x);
}
catch (FormatException)
{
d = null;
}
return new { v = x, d, s = x.ToString() };
}).GroupBy(x => x.d.HasValue)
.Select(g =>
g.Key
? g.OrderBy(x => x.d.Value).Select(x => x.v)
: g.OrderBy(x => x.s).Select(x => x.v))
.SelectMany(x => x)
.ToList();
Returns all numeric values first, (sorted using its value) and then all strings sorted using standard string comparison.
List<object> list = new List<object>() { 11.21, 323, 4, 3221, 221, 2, "asd" };
var qu = list.OrderBy((o) => o.ToString());
qu.ToList().ForEach((e) => Console.WriteLine(e));
You must cast your items to something, not necessarily to int. In this example I casted everything to string. Just bear in mind that the OrderBy operation will sort by string and not numerically of course. You can't sort numerically because you have a string item in your list.
I would use a custom comparer:
o = o.OrderBy(x => x, new MyComparer());
...
internal class CustomComparer : IComparer<object>
{
public int Compare(object x, object y)
{
if (x is string && y is string)
{
return ((string)x).CompareTo((string)y);
}
else if (x is string && IsNumber(y))
{
return -1;
}
else if (y is string && IsNumber(x))
{
return 1;
}
else if (IsNumber(x) && IsNumber(y))
{
return (Convert.ToDouble(x)).CompareTo(Convert.ToDouble(y));
}
else
{
throw new NotSupportedException();
}
}
private bool IsNumber(object o)
{
var t = o.GetType();
if (o is int || o is double || o is float || o is long)
return true;
return false;
}
}
How about implementing IComparer<T> like this ?
public class MyComparer : IComparer<object>
{
public int Compare(object x, object y)
{
var type = x.GetType();
var type2 = y.GetType();
if (type == typeof(string) && type2 == typeof(string))
{
return String.Compare((string)x, (string)y);
}
else if (type.IsValueType && type2.IsValueType)
{
var comp = Comparer<double>.Default;
return comp.Compare(Convert.ToDouble(x), Convert.ToDouble(y));
}
else
{
return 0;
}
}
}
And use it:
List<object> list = new List<object>() { 11.21, 323, 4, 3221, 221, 2, "asd" };
var qu = list.OrderBy(o => o, new MyComparer());
qu.ToList().ForEach((e) => Console.WriteLine(e));
I have this C# code that works just fine, which grabs any two fields and puts them in a list for a Drop Down List:
var myDDL = GetDDLValues<Product>( myContact, "contactid", "companyname");
I would like it to take the two string parameters in something other than strings. This would be really nice to do:
GetDDLValues<Product>( myContact, p => p.contactid, p => p.companyname)
This is the code it calls (reflection by sweko Thanks!):
private object GetProperty(object obj, string propertyName)
{
PropertyInfo pi = obj.GetType().GetProperty(propertyName);
object value = pi.GetValue(obj, null);
return value;
}
public IList<DDLValues> GetDDLValues<T>(IList<T> objectListToMap,
string textProperty, string valueProperty)
{
if( objectListToMap != null && objectListToMap.Count > 0)
{
Mapper.CreateMap< T, DDLValues>()
.ForMember( dest => dest.text,
opt => opt.MapFrom(src => textProperty))
.ForMember( dest => dest.value,
opt => opt.MapFrom(src => valueProperty));
return Mapper.Map<IList<T>, IList<DDLValues>>(objectListToMap);
}
else
{
return null;
}
}
To build a dynamic query from string:
public class Product
{
public long ID { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
}
static void Main(string[] args)
{
List<Product> products = (from i in Enumerable.Range(1, 10)
select new Product { ID = i, Name = "product " + i, Date = DateTime.Now.AddDays(-i) }).ToList(); //the test case
const string SortBy = "Date"; // to test you can change to "ID"/"Name"
Type sortType = typeof(Product).GetProperty(SortBy).PropertyType; // DateTime
ParameterExpression sortParamExp = Expression.Parameter(typeof(Product), "p"); // {p}
Expression sortBodyExp = Expression.PropertyOrField(sortParamExp, SortBy); // {p.DateTime}
LambdaExpression sortExp = Expression.Lambda(sortBodyExp, sortParamExp); // {p=>p.DateTime}
var OrderByMethod = typeof(Enumerable).GetMethods().Where(m => m.Name.Equals("OrderBy") && m.GetParameters().Count() == 2).FirstOrDefault().MakeGenericMethod(typeof(Product), sortType);
var result = OrderByMethod.Invoke(products, new object[] { products, sortExp.Compile() });
}
tvanfosson is right that a Select() is simplest; if you want to use AutoMapper, you want something like this..
public IList<DDLValues> GetDDLValues<T>(IList<T> objectListToMap,
Func<T, string> textSelector, Func<T, string> valueSelector)
{
if( objectListToMap == null || objectListToMap.Count == 0)
return null;
Mapper.CreateMap< T, DDLValues>()
.ForMember( dest => dest.text,
opt => opt.MapFrom(textSelector))
.ForMember( dest => dest.value,
opt => opt.MapFrom(valueSelector));
return Mapper.Map<IList<T>, IList<DDLValues>>(objectListToMap);
}
If DDLValues has a constructor that takes the text and value properties it should be relatively simple.
IList<DDLValues> GetDDLValues<T>( IList<T> source, Func<T,DDLValues> selector )
where T : class
{
return source.Select( selector )
.ToList();
}
Called as
var ddlList = GetDDLValues( contacts, p => new DDLValues( c.Name, p.ContactID.ToString() ) );
This presumes that DDLValues has a constructor like:
public DDLValues( string text, string value ) { ... }
If you're pulling from the database in your source, then you may also need to materialize the query using ToList() before you apply the selector to ensure it doesn't try to convert the selector into a SQL expression.