DynamicMethods allow you to specify a target instance for the delegate you create. However, it appears that this does not work when you use a struct type. It fails with an exception telling me it cannot bind to this method. Is the error because my IL does not unbox the target instance?
If I change A here to a class it works without issue. What am I doing wrong?
(Also please do not suggest calling Delegate.CreateDelegate to bind to the GetType method with a target instance)
Here is a sample repro:
struct A { }
... //then some where in code::
Func<Type> f = CodeGen.CreateDelegate<Func<Type>>(il=>
il.ldarga_s(0)
.constrained(typeof(A))
.callvirt(typeof(object).GetMethod("GetType"))
.ret(),
name:"Constrained",
target:new A()
);
Note: I'm using the Emitted library for the fluent interface for IL. Also Here is the code for the CodeGen Method.
public static class CodeGen
{
public static TDelegate CreateDelegate<TDelegate>(Action<ILGenerator> genFunc, string name = "", object target = null, bool restrictedSkipVisibility = false)
where TDelegate:class
{
ArgumentValidator.AssertGenericIsDelegateType(() => typeof(TDelegate));
ArgumentValidator.AssertIsNotNull(() => genFunc);
var invokeMethod = typeof(TDelegate).GetMethod("Invoke");
var #params = invokeMethod.GetParameters();
var paramTypes = new Type[#params.Length + 1];
paramTypes[0] = target == null ? typeof(object) : target.GetType();
#params.ConvertAll(p => p.ParameterType)
.CopyTo(paramTypes, 1);
var method = new DynamicMethod(name ?? string.Empty, invokeMethod.ReturnType, paramTypes, restrictedSkipVisibility);
genFunc(method.GetILGenerator());
return method.CreateDelegate<TDelegate>(target);
}
}
See the important note at http://msdn.microsoft.com/en-us/library/74x8f551.aspx, which also applies here:
If method is static (Shared in Visual Basic) and its first parameter
is of type Object or ValueType, then firstArgument can be a value
type. In this case firstArgument is automatically boxed. Automatic
boxing does not occur for any other arguments, as it would in a C# or
Visual Basic function call.
The implication is that the first argument to your dynamic method will need to be of type object, and you'll need to do a ldarg_0 followed by an unbox before doing the constrained call.
Related
For some library debugging usage I am developing a custom attribute which would be able to match existing methods.
For example the following:
[NetMethodAliasMethod("TryParse", Parameters = new Type[] { typeof(string), typeof(char)})]
...should match static bool System.Char.TryParse (string? s, out char result). The attribute "Parameters" are tested against ParameterInfo.ParameterType MethodInfo.GetParameters() to match the parameters (of course name is matched as well).
In case of the string? this works because the nullable attribute is not part of the type. However the type returned from MethodInfo for the 2nd parameter is char&, while typeof(char&) is not allowed. Is there any possible way to pass this type ?
I was thinking about generic delegates, but see no way as the use of delegates is to be specific on which methods are compatible. Another Idea was to use typeof(System.Char).GetMethod("TryParse"). But this causes the same trouble if multiple overloads exist, where we would have to use GetMethods (with 's') instead and again match the parameter types.
Of course I could use string checking of the type names, but that should be the last resort if no other way is possible. But since Type(char&) exists, I hope there is a way to get it ?
You can make such a type by using the MakeByRefType method.
However you cannot actually call this method when writing your attribute, because something like typeof(int).MakeByRefType() is not an attribute argument expression (defined at the very bottom of this section).
Therefore, a workaround I would go for is to declare an additional parameter for your attribute that encodes where these "by refs" are:
public bool[] ParametersAreByRef { get; set; }
And then do:
[NetMethodAliasMethod(
"TryParse",
Parameters = new Type[] { typeof(string), typeof(char)}
ParametersAreByRef = new bool[] { false, true })]
Only when you retrieve the attribute later down the line, do you use MakeByRefType to recreate the Types depending on the values in ParametersAreByRef.
var modifiedParameters = attribute.Parameters.Zip(
attribute.ParametersAreByRef, (t, b) => b ? t.MakeByRefType() : t
).ToArray()
If you don't like passing ParametersAreByRef every time for methods with all non-by-ref parameters, you can make Parameters a positional parameter (put it as a constructor parameter), and then you can initialise ParametersAreByRef to Parameters.Select(x => false).ToArray() by default or something like that in the constructor.
As Jeroen Mostert suggested in the comments, another workaround is to use pointer types, seeing as they are very rare in actual code, to encode by-ref-ness:
Parameters = new Type[] { typeof(string), typeof(char*)}
When you retrieve the attribute instance, check IsPointer and use GetElementType:
var modifiedParameters = attribute.Parameters.Select(
x => x.IsPointer ? x.GetElementType().MakeByRefType() : x
).ToArray();
You can also do this in the setter of Parameters if it has a backing field.
Alternatively, make your own dummy ByRef<T> type so that by-ref-ness can be annotated:
public abstract class ByRef<T> {}
…
Parameters = new Type[] { typeof(string), typeof(ByRef<char>)}
Then
// e.g. in the setter of Parameters
_parameters = Parameters.Select(
x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ByRef<>) ? x.GetGenericArguments()[0].MakeByRefType() : x
).ToArray();
I like the elegance of #JeroenMostert comment so I did add a ByRef abstract generic class to my attribute:
public abstract class ByRef<T> { };
and modified the Parameters property to resolve the type
private Type[]? _parameters = null;
public Type[]? Parameters
{
get { return _parameters; }
set
{
if(value==null)
{
_parameters = null;
return;
}
for(int i=0; i<value.Length; i++)
{
Type type = value[i];
if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ByRef<>))
{
value[i] = type.GetGenericArguments()[0].MakeByRefType();
}
_parameters = value;
}
}
}
This setter resolves all ByRef to T&
Note that in my case here, Parameters is nullable because I use null as parameter wildcard (i.e. matching any types and count).
I am currently working on adding a new feature to an existing API. Let's say I have a PerformTediousOperation method with a generic type parameter:
void PerformTediousOperation<T>()
This method is inside a Operator class, and can be invoked like this:
operatorInstance.PerformTediousOperation<T>()
I want to create a new Operator instance and invoke this method, whenever the user marks a type with the Operable attribute.
Currently, this is where I am stuck:
MethodReference performTediousOperationMethodReference =
new MethodReference(
name: "PerformTediousOperation",
returnType: moduleDefinition.TypeSystem.Void,
declaringType: operatorTypeReference)
{
HasThis = true
};
The emitted IL code (in C#) is simply PerformTediousOperation();.
How can I fix this so that the emitted code is instead PerformTediousOperation<T>(), where T will be determined at runtime?
Please let me know if any more information is desired.
Here is an example of how to generate a method that has a generic type parameter using MonoCecil:
MethodReference performTediousOperationMethodReference =
new MethodReference(
name: "PerformTediousOperation",
returnType: moduleDefinition.TypeSystem.Void,
declaringType: operatorTypeReference)
{
HasThis = true
};
var genericParameter = new GenericParameter("T", performTediousOperationMethodReference);
performTediousOperationMethodReference.GenericParameters.Add(genericParameter);
GenericInstanceMethod performTediousOperationInstanceMethod =
new GenericInstanceMethod(performTediousOperationMethodReference)
{
GenericArguments = { moduleDefinition.ImportReference(typeof(int)) }
};
This generates PerformTediousOperation<int>().
You may pass any other TypeReference instance to the GenericArguments field, and the output will differ accordingly. E.g. if you pass in moduleDefinition.ImportReference(typeof(string)) instead, the output will be PerformTediousOperation<string>().
i'm kind of struggling with a ML.NET related problem here and am hoping that someone might can help me.
I'm developing an (.NET core) application which consumes ONNX models whose inputs are unknown at compile time. What I've done so far:
I'm able to compile an assembly during runtime which contains the input class definition and load this definition:
var genericSampleAssembly =
AssemblyLoadContext.Default.LoadFromAssemblyPath("/app/storage/sample.dll");
Type genericInputClass = genericSampleAssembly.GetType("GenericInterface.sample");
Also I'm able to train a model with that dynamically created Inputtype using Reflection:
MethodInfo genericCreateTextLoader = typeof(TextLoaderSaverCatalog).GetMethods()
.Where(_ => _.Name == "CreateTextLoader")
.Single(_ => _.GetParameters().Length == 6)
.MakeGenericMethod(_genericInputClass);
TextLoader reader = genericCreateTextLoader.Invoke(_mlContext.Data, new object[] { _mlContext.Data, false, ',', true, true, false}) as TextLoader;
IDataView trainingDataView = reader.Read("sample.txt");
var debug = trainingDataView.Preview();
var pipeline = _mlContext.Transforms.Concatenate("Features", _featureNamesModel
.AppendCacheCheckpoint(_mlContext)
.Append(_mlContext.Regression.Trainers.StochasticDualCoordinateAscent(labelColumn: "Label",
featureColumn: "Features")));
ITransformer model = pipeline.Fit(trainingDataView);
But I'm not able to make predictions by now because I don't know how to invoke the PredictionEngine. I'm able to get a generic version of that CreatePredictionEngine method but don't now how to cast that returning object to an PredictionEngine and finally invoke the Predict method:
MethodInfo genericCreatePredictionEngineMethod = typeof(PredictionEngineExtensions).GetMethods()
.Single(_ => _.Name == "CreatePredictionEngine")
.MakeGenericMethod(new Type[] { genericInputClass, typeof(GenericPrediction)});
var predictionEngine = genericCreatePredictionEngineMethod.Invoke(_model, new object[] {_model, _mlContext, null, null});
predictionEngine is of Type object in this case but I need to cast it to something like PredictionEngine<genericInputClass, GenericPrediction>, while genericInputClass is the class from that dynamically created assembly and GenericPrediction is a simple class with one output I know at compile time.
So whats missing is something like:
MethodInfo genericCreatePredictionEngineMethod = typeof(PredictionEngineExtensions).GetMethods()
.Single(_ => _.Name == "CreatePredictionEngine")
.MakeGenericMethod(new Type[] { genericInputClass, typeof(GenericPrediction)});
PredictionEngine<genericInputClass, GenericPrediction> predictionEngine = genericCreatePredictionEngineMethod.Invoke(_model, new object[] {_model, _mlContext, null, null}) as PredictionEngine<genericInputClass, GenericPrediction>;
float prediction = predictionEngine.Predict(genericInputClass inputValue);
Does anyone had a similar problem or has any other hints?
I might missed some lines, because I copy/pasted and simplified it pretty quick. In case something is missing, I'll provide it later.
EDIT: I constructed a minimal example to show the basic problem. As mentioned in the comments dynamic isn't possible due to the ML.NET methods.
using System;
using System.Linq;
using System.Runtime.Loader;
namespace ReflectionSample
{
class Program
{
static void Main(string[] args)
{
// Example with a known Type
var extendedClass = new DummyExtendedClass();
SampleGenericClass<String> sampleGenericClass = extendedClass.SampleGenericExtensionMethod<String>();
sampleGenericClass.SampleMethod("");
// At compile time unknown Type - In reality the loaded dll is compiled during runtime
var runtimeCompiledSampleAssembly =
AssemblyLoadContext.Default.LoadFromAssemblyPath("C:/Program Files (x86)/Reference Assemblies/Microsoft/Framework/.NETCore/v4.5/System.IO.dll");
var compileTimeUnknownClass = runtimeCompiledSampleAssembly.GetType("System.IO.TextReader");
var reflectedExtensionMethod = typeof(Extensions).GetMethods()
.Single(_=>_.Name== "SampleGenericExtensionMethod")
.MakeGenericMethod(new[] {compileTimeUnknownClass});
var howToCastThis = reflectedExtensionMethod.Invoke(extendedClass, new object[] {extendedClass});
// whats missing:
// howToCastThis is of Type object but should be SampleGenericClass<System.IO.TextReader>
// I need to be able to call howToCastThis.SampleMethod(new System.IO.TextReader)
// I thought this might work via reflecting SampleMethod and MakeGenericMethod
Console.ReadKey();
}
}
public sealed class SampleGenericClass<T>
{
public void SampleMethod(T typeInput)
{
Console.WriteLine($"Invoking method worked! T is of type {typeof(T)}");
}
}
public static class Extensions
{
public static SampleGenericClass<T> SampleGenericExtensionMethod<T>(this DummyExtendedClass extendedClass)
{
Console.WriteLine($"Invoking extension method worked! T is of type {typeof(T)}");
return new SampleGenericClass<T>();
}
}
public class DummyExtendedClass
{
public DummyExtendedClass() { }
}
}
Greetings
Sven
Nice job on the MCVE. I was able to invoke SampleMethod; it turns out there's not much to it and it's probably less complicated than you imagined.
In your example, the object you're getting, howToCastThis, is of a type that's already close-constructed. As long as you start from that instance's type, you don't need to use MakeGenericMethod.
Let's say you have an object instance, compileTimeTypeUnknownInstance, for the parameter you want to pass to SampleMethod. Since System.IO.TextReader is abstract, compileTimeTypeUnknownInstance would have to be of a concrete, TextReader-derived type. With those conditions satisfied, the following works:
var sampleMethod = howToCastThis.GetType().GetMethods()
.Single(mi => mi.Name == "SampleMethod");
sampleMethod.Invoke(howToCastThis, new object[] { compileTimeTypeUnknownInstance });
SampleMethod reports that T is of type System.Text.TextReader.
Again, howToCastThis is of a close-constructed type, therefore, so is the method you want.
Note: Although not the case here, a method in a close-constructed type can introduce additional type arguments so you still would have to call MakeGenericMethod to close-construct the method in that case.
Now, if I were to try to translate this to your situation, I'm guessing it would look something like this:
var predictMethod = predictionEngine.GetType().GetMethods()
.Single(mi => mi.Name == "Predict");
float prediction = (float)predictMethod.Invoke(predictionEngine, new object[] { inputValue });
I'm not sure about the syntax in your pseudocode call to Predict. I assumed that inputValue was the only parameter and genericInputClass was only there to indicate that it was a type argument in the close-constructed type. If this was incorrect, you'll need to figure out what actually goes in that object[] argument.
I have this method, using Expressions to create fields getters:
public static Func<object, T> CreateFieldValueGetter<T>(this Type declaringType, FieldInfo fieldToGet) {
var paramExp = Expression.Parameter(typeof(object));
// ArgumentException if declaringType describes generic-type:
var cast = Expression.Convert(paramExp, declaringType);
var body = Expression.Field(cast, fieldToGet);
return Expression.Lambda<Func<object, T>>(body, paramExp).Compile();
}
It works great until I give it a generic type like:
class DataErrorNotifyingViewModelBase<TErr> : ViewModelBase, INotifyDataErrorInfo
where TErr : struct, IConvertible, IComparable, IFormattable
{
// ...
}
This way:
var vm = new DataErrorNotifyingViewModelBase<MyErrorsTypeEnum> ();
var type = vm.GetType();
// ArgumentException:
var getter = type.CreateFieldValueGetter<PropertyChangedEventHandler>(type.GetField("PropertyChanged"));
This is the exception I get:
Exception thrown: 'System.ArgumentException' in System.Core.dll
Additional information: Type GuiHelpers.DataErrorNotifyingViewModelBase`1[TErr] is a generic type definition
although simple casting works:
var vm = new DataErrorNotifyingViewModelBase<PrintDialogError>();
var obj = (object) vm;
So how can I feed it with generic types? Am I limited to non-generic-types only?
Edit - solution:
Kaveh Hadjari caught it:
Passing t = typeof (Dictionary<T, int>) will raise ArgumentException, as t.GetGenericArguments()[0].IsGenericParameter is true (albeit t.GetGenericArguments()[1].IsGenericParameter is false!)
Passing the type t = typeof (Dictionary<int, int>) works fine, becuse no element of the t.GetGenericArguments() array has IsGenericParameter == true
A generic type is a template for many different specialized types, and at runtime theres a difference between the generic type and the "instanced" types. A possible reason the call to Expression.Convert might be failing could be you're providing it with the type of the generic version and not with a specialized version with type variables set.
Update: I imagine there's a good reason this method would never work with generic types. Consider the case if the type variable is used as type for a field in the generic class. Since the type size (reference, Boolean, short, int, long, etc) could be variable it would mean that it could offset the memory address of other fields in different specializations of the generic class in a variable way. How would you know in advance which field length thus address offset would be the case if all the variables where not set? You couldn't and therefor we can't determine the the address of the field we might want to create a getter for. The only solution would be to actually create a getter that would rely on using reflection on each object you call the getter with, which would incur higher costs than imagined and if you are happy with that solution you might be as well have a single method that gets the value of the field using reflection without actually creating these getters in the first place.
"Am I limited to non-generic-types only?"
No, of course not. The error message is clear, though not consistent with the code example you provided. You seem to be passing for the type the original generic type definition (i.e. with an unspecified value for the type parameter), not the constructed generic type (i.e. with a specified value for the type parameter).
Unfortunately, without a good, minimal, complete code example that reliably reproduces the problem, it's impossible to know precisely what you've done wrong. All I can say is that you did do something wrong. If you want more specific advice than that, please edit your post so that it includes a good code example.
For what it's worth, here's a complete code example that demonstrates your method working fine with a generic type:
class A<T>
{
public int field1;
public T field2;
public event EventHandler Event1;
}
class Program
{
static void Main(string[] args)
{
A<bool> a = new A<bool>();
Func<object, int> field1Getter =
CreateFieldValueGetter<int>(a.GetType(), a.GetType().GetField("field1"));
Func<object, bool> field2Getter =
CreateFieldValueGetter<bool>(a.GetType(), a.GetType().GetField("field2"));
Func<object, EventHandler> event1Getter =
CreateFieldValueGetter<EventHandler>(a.GetType(), a.GetType()
.GetField("Event1", BindingFlags.NonPublic | BindingFlags.Instance));
}
static Func<object, T> CreateFieldValueGetter<T>(Type declaringType, FieldInfo fieldToGet)
{
var paramExp = Expression.Parameter(typeof(object));
// ArgumentException if declaringType describes generic-type:
var cast = Expression.Convert(paramExp, declaringType);
var body = Expression.Field(cast, fieldToGet);
return Expression.Lambda<Func<object, T>>(body, paramExp).Compile();
}
}
The only wrinkle here is that to obtain the field for the event, you have to specify the BindingFlags appropriate to that field (in particular, it's non-public so the default search of GetField() won't find it). The code you showed does this incorrectly, but it doesn't explain the exception you're getting.
Type valueType = Type.GetType("int");
object value = new List<valueType>();
The first line compiles fine, But the 2nd does not.
How can I create a generic list (or call a generic method)
object value = foo<valueType>();
By only having a string representation of the type?
My end goal is actually to take two string "int" and "5 (as an example) and assign the value of 5 to the object [and eventually to the userSettings]. But I have a method that will convert "5" to the actual value if I can tell the generic method it is of type int based on the string representation.
T StringToValue<T>(string s)
{
return (T)Convert.ChangeType(s, typeof(T));
}
Update: I was thinking that creating a generic object and calling a generic method would use the same methodology, but I guess I was wrong. How can I call the generic method?
Type.GetType("int") returns null. This is invalid because int is just a keyword in the C# language, which is equivalent to the type System.Int32. It has no special meaning to the .NET CLR, so it's not usable in reflection. You might have meant typeof(int) or Type.GetType("System.Int32") (or it doesn't really matter, because that was just an example).
Anyway, once you have the right Type, this is how you can get your list. The key is MakeGenericType.
Type valueType = typeof(int);
object val = Activator.CreateInstance(typeof(List<>).MakeGenericType(valueType));
Console.WriteLine(val.GetType() == typeof(List<int>)); // "True" - it worked!
I will share an example from Jeffrey Richter's book CLR Via C# about constructing generic types, this is not specific to the question but will help guide you to finding the appropriate way of doing what you want:
public static class Program {
public static void Main() {
// Get a reference to the generic type's type object
Type openType = typeof(Dictionary<,>);
// Close the generic type by using TKey=String, TValue=Int32
Type closedType = openType.MakeGenericType(typeof(String), typeof(Int32));
// Construct an instance of the closed type
Object o = Activator.CreateInstance(closedType);
// Prove it worked
Console.WriteLine(o.GetType());
}
}
Will display: Dictionary`2[System.String,System.Int32]
try this:
Type valueType = Type.GetType("System.Int32");
Type listType = typeof(List<>).MakeGenericType(valueType);
IList list = (IList) Activator.CreateInstance(listType);
// now use Reflection to find the Parse() method on the valueType. This will not be possible for all types
string valueToAdd = "5";
MethodInfo parse = valueType.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static);
object value = parse.Invoke(null, new object[] { valueToAdd });
list.Add(value);