Use non-generic method of Hashset through reflection - c#

I want to be able to call the Hashset.ExceptWith() method using reflection but I can't make it happen.
I can't create a generic method. It seems it isn't a generic method.
I also can't seem to call the method itself using invoke.
Does anyone know how to do this?
var newSet = pi.GetValue(newItem);
var existingSet = pi.GetValue(existingItem);
MethodInfo methodExcept = typeof(HashSet<>).GetMethod("ExceptWith");
//The next line throws: Void ExceptWith(System.Collections.Generic.IEnumerable`1[T]) is not a GenericMethodDefinition.
//MakeGenericMethod may only be called on a method for which MethodBase.IsGenericMethodDefinition is true.'
MethodInfo genericExcept = methodExcept.MakeGenericMethod(pi.GetType().GetGenericTypeDefinition().UnderlyingSystemType);
//The next line throws 'Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.'
methodIntersect.Invoke(existingSet, new[] { newSet });
//The next line is not possible since instantiation of the variable doesn't work
genericIntersect.Invoke(existingSet, new[] { newSet });

Ok, so this seems to work. Apparently you have to get the GenericArguments from the Type first.
var newSet = pi.GetValue(newItem);
var existingSet = pi.GetValue(existingItem);
var itemsType = existingSet.GetType().GetGenericArguments()[0];
MethodInfo methodExcept = typeof(HashSet<>).MakeGenericType(itemsType).GetMethod("ExceptWith");
methodExcept.Invoke(newSet, new[] { existingSet });

Related

Getting data from a string defined table name filtered on a string defined field

I am trying to return the contents of a Where filtered table of dynamic type based on a dynamic field name from an array of values.
Heres what I have so far:
public JsonResult GetRelationShips(string linkingTable, string idField, int[] ids)
{
var tableType = typeof(context).GetProperty(linkingTable);
var entityTable = tableType.GetValue(db) as IQueryable;
var method = typeof(List<int>).GetMethod("Contains");
var eParam = Expression.Parameter(tableType.PropertyType.GetGenericArguments()[0]);
var call = Expression.Call(Expression.Constant(ids.ToList()), method, Expression.Property(eParam, idField));
var func = typeof(Func<,>);
var genericFunc = func.MakeGenericType(tableType.PropertyType.GetGenericArguments()[0], typeof(bool));
var lambda = Expression.Lambda(genericFunc, call, eParam);
var results = typeof(System.Linq.Enumerable).GetMethods().Where(x => x.Name == "Where").First().Invoke(db, new object[] { lambda });
return Json(results);
}
That last line is giving me an error:
Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
Honestly, I cobbled this together this afternoon from snippets from all over the internet. I have no idea what I'm doing here, this is new to me and I'm keen to learn. Just trying to avoid SQL injection, the rest of the project is entirely Linq so I'm soldiering on. I'm learning Generic types too, got my head around that, don't see how I can use them here though.
There any many flaws in this single line of code:
var results = typeof(System.Linq.Enumerable).GetMethods().Where(x => x.Name == "Where").First().Invoke(db, new object[] { lambda });
Trying to call Enumerable.Where instead of Queryable.Where. This would cause retrieving the whole table data and performing the filtering in memory instead of the database side.
Trying to call potentially wrong method. Where has 2 overloads, and it's undefined which one will be returned as first by the reflection.
Trying to invoke generic method definition, causing the exception you are getting. You have to first construct a generic method by using MakeGenericMethod and invoke that.
Trying to invoke static generic extension method via reflection as if it is instance method. Instead, you should pass null as first argument to Invoke and pass new object[] { entityTable, lambda } as second argument.
You can avoid all these traps by simply using the C# dynamic method dispatch:
IQueryable results = Queryable.Where((dynamic)entityTable, (dynamic)lambda);
The whole code can be simplified by using the following Expression.Call overload:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments);
which is very useful for "calling" static generic extension methods:
var query = (IQueryable)db.GetType().GetProperty(linkingTable).GetValue(db);
// e =>
var entity = Expression.Parameter(query.ElementType, "e");
// ids.Contains(e.idField)
// = Enumerable<int>.Contains(ids, e.idField)
var containsCall = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.Contains),
new Type[] { typeof(int) },
Expression.Constant(ids),
Expression.Property(entity, idField)
);
// e => ids.Contains(e.idField)
var predicate = Expression.Lambda(containsCall, entity);
// query = query.Where(predicate);
query = Queryable.Where((dynamic)query, (dynamic)predicate);
You can also avoid the dynamic Where call and use similar Expression.Call based approach to "call" it, combined with IQueryProvider.CreateQuery:
// query.Where(predicate)
// = Queryable.Where<ElementType>(query, predicate)
var whereCall = Expression.Call(
typeof(Queryable),
nameof(Queryable.Where),
new Type[] { query.ElementType },
query.Expression,
predicate
);
// query = query.Where(predicate)
query = query.Provider.CreateQuery(whereCall);
I've provided all that just because you said you are keen to learn. The simplest way of handling such tasks (and not reinvent the wheel) is to use some 3rd party package. For instance, with System.Linq.Dynamic package the whole code would be:
var query = ((IQueryable)db.GetType().GetProperty(linkingTable).GetValue(db))
.Where($"#0.Contains({idField})", ids);

Generic method info from List(T) by type and dynamic parsing

Is it possible to get methodInfo based on wanted type like this:
var wantedType = typeof(propertyReference);
var methodInfo = typeof(List<wantedType>).GetMethod("Contains",
new[] { typeof(wantedType) });
...and also this:
var list ="(some, list, here)".Split(new[] { ',' },
StringSplitOptions.RemoveEmptyEntries).Select(wantedType.Parse).ToList()
If yes, what would be the proper way to do it?
As wantedType is a runtime-type you need to get the generic definition of the type before. Use MakeGenericType to achieve this:
var t = typeof(List<>).MakeGenericType(wantedType);
Now you can call any of its method via reflection as if it where a normal type:
t.GetMethod("Contains").Invoke(myList, new[] { instanceOfWantedType });
where MyValue is an instance of the type wantedType.
Your second example works similar, just call the Parse-method via reflection:
var m = wantedType.GetMethod("Parse", new[] { typeof(string) });
var list = stringArray.Select(x => m.Invoke(null, new[] { x })).ToList()
But be aware that the list is quite unspecific, as all is elements are of type object.

How to set Generic Type T as string in C#

I Have the full name of a type as string and I would like to use this string to tell type of a generic method. see this
string typeStr ="Dll.NameSpace.MyType";
var list = new List<Dll.NameSpace.MyType>();//should put typeStr here
var result = call.SomeGenericMethod<...>(obj);//should put typeStr here
This answer didn't help me. If you try visiting this other one, it does not satisfy my needs.
EDITS: The generic method I'm trying to call is an extension one. I have unsuccessfully tried the following code,
method remains null
var result = repo.Get();
Type elementType =
Type.GetType("x2o_Care.Models.ViewModels.PatientViewModel");
MethodInfo method =typeof(AutoMapper.QueryableExtensions.Extensions)
.GetMethod("ProjectTo", new[] { typeof(IQueryable)});
MethodInfo generic = method.MakeGenericMethod(elementType);
var model = generic.Invoke(result.OrderBy(e => true).Take(20), null);
Your code is exactly what you need. You just need to specify exactly what overload of ProjectTo method you need as there are a few. Make sure to specify all the parameters in GetMethod/
For example:
MethodInfo method =typeof(AutoMapper.QueryableExtensions.Extensions)
.GetMethod("ProjectTo", new[] { typeof(IQueryable),
typeof(object),
typeof(Expression<Func<TDestination, object>>[])
});
Here is a list of all the ProjectTo methods:
https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/QueryableExtensions/Extensions.cs

Method.Invoke failing with Parameter Count Mismatch

I am trying to invoke a generic method. The definition of the method is as follows:
public System.Collections.Generic.IList<T> Query<T>(string query, [string altUrl = ""])
where T : new()
This is from the SalesforceSharp library on github. I am trying to make an additional service layer over this call and am struggling to invoke it. See my code below.
public List<T> Query<T>()
{
//IList<Salesforce.Account> _returnList = null;
IList<T> _returnList = null;
Type _t = typeof(T);
SqlBuilder _sb = new SqlBuilder();
_sb.Table = _t.Name.ToString();
foreach (PropertyInfo p in _t.GetProperties()) _sb.Fields.Add(p.Name.ToString());
MethodInfo method = _Client.GetType().GetMethod("Query");
method = method.MakeGenericMethod(_t);
try
{
object[] _prms = new object[1];
_prms[0] = _sb.SQL;
_returnList = (IList<T>)method.Invoke(_Client, new object[] { _prms });
//_returnList = _Client.Query<Salesforce.Account>(_sb.SQL);
}
catch { }
return (List<T>)_returnList;
}
If I run this i get a Parameter Count Mismatch exception on the method.invoke line, but i am confused because if i bring in the two uncommented lines and execute without the generic call it is working ok. I have tried many combinations of string arrays wrapped in object arrays, strings in strings, etc but can't get it to go. I thought maybe it was treating the second parameter as mandatory? but adding another object to my _prms array didnt work either.
Please help!
Thanks,
Dom
For optional parameters that you don't want to specify a value for, you have to pass Type.Missing like this:
_returnList = (IList<T>)method.Invoke(_Client, new object[] { _sb.SQL, Type.Missing });
Quoting from this reference:
Use the Missing field for invocation through reflection to obtain the default value of a parameter
Please note also that another problem is that you are currently passing an object[] instead of just the query string.
The optional parameters are just syntactic sugars, when you don't supply an optional parameter compiler calls the method with the given default value. But in case of Reflection, you need to do it manually.
object[] _prms = new object[2];
_prms[0] = _sb.SQL;
_prms[1] = "";
_returnList = (IList<T>)method.Invoke(_Client, _prms);

Create generic Func from reflection

I've specified type in a variable: Type hiddenType. I need to create a Func<T> delegate where T is of type specified in mentioned variable and assign an method:
var funcType = typeof(Func<>).MakeGenericType(hiddenType);
Func<object> funcImplementation = () => GetInstance(hiddenType);
var myFunc= Delegate.CreateDelegate(funcType , valueGenerator.Method);
It doesn't works - because funcImplementation is returns object instead of desired. At runtime, it will surely be an instance of type specified in hiddenType.
GetInstance returns object and signaure cannot be changed.
You can solve this by building an expression tree manually, and inserting a cast to hiddenType. This is allowed when you construct an expression tree.
var typeConst = Expression.Constant(hiddenType);
MethodInfo getInst = ... // <<== Use reflection here to get GetInstance info
var callGetInst = Expression.Call(getInst, typeConst);
var cast = Expression.Convert(callGetInst, hiddenType);
var del = Expression.Lambda(cast).Compile();
Note: the above code assumes that GetInstance is static. If it is not static, change the way you construct callGetInst to pass the object on which the method is invoked.
Instead of using a Type, you could consider using a generic wrapper, if it's not possible for you to change the GetInstance signature:
private Func<THidden> GetTypedInstance<THidden>()
{
return () => (THidden)GetInstance(typeof(THidden));
}
Then you can just call it with
GetTypedInstance<SomeClass>();
instead of
GetInstance(typeof(SomeClass));

Categories

Resources