Reputation: 1368
I have a method that currently creates a dynamic type at run time. The method is largely taken from this thread. The code works as is and create the type just fine. However, later in the code I need to use this type as a generic. This is where I'm a bit stuck. The part where the generic type is used is here:
mlContext.Model.CreatePredictionEngine<TSrc,TDst>(ITransformer, DataViewSchema)
where the TDst is known prior to compile time, but TSrc is not. TSrc in this is example is my dynamic type which is created in this method:
public static Type CreateDynamicType(IEnumerable<DynamicTypeProperty> properties)
{
StringBuilder classCode = new StringBuilder();
// Generate the class code
classCode.AppendLine("using System;");
classCode.AppendLine("namespace Dexih {");
classCode.AppendLine("public class DynamicClass {");
foreach (var property in properties)
{
classCode.AppendLine($"public {property.Type.Name} {property.Name} {{get; set; }}");
}
classCode.AppendLine("}");
classCode.AppendLine("}");
var syntaxTree = CSharpSyntaxTree.ParseText(classCode.ToString());
var references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(DictionaryBase).GetTypeInfo().Assembly.Location)
};
var compilation = CSharpCompilation.Create("DynamicClass" + Guid.NewGuid() + ".dll",
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
var result = compilation.Emit(ms);
if (!result.Success)
{
var failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
var message = new StringBuilder();
foreach (var diagnostic in failures)
{
message.AppendFormat("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
throw new Exception($"Invalid property definition: {message}.");
}
else
{
ms.Seek(0, SeekOrigin.Begin);
var assembly = Assembly.Load(ms.ToArray());
var dynamicType = assembly.GetType("Dexih.DynamicClass");
return dynamicType;
}
}
}
Can anyone describe how to modify the CreateDynamicType method so that it can return a generic type which can be used in the CreatePredictionEngine function?
EDIT (based on Kit's answer)
At the very bottom, I create an instance of the prediction engine by invoking the generic method. However, that simply returns a generic object type. I need to be able to then call engine.Predict(args) to be able to actually make a prediction using the engine (see docs here). I tried casting the return object to a PredictionEngine type, but that didn't seem to work. Do I have to create a new generic method for the Predict method too?
var dynamicType = DynamicType.CreateDynamicType(properties);
var inputSchema = SchemaDefinition.Create(dynamicType);
var outputSchema = SchemaDefinition.Create(typeof(ModelOutput));
var dataType = mlContext.Model.GetType();
var createEngineGeneric = dataType.GetMethods().First(method => method.Name == "CreatePredictionEngine" && method.IsGenericMethod);
var genericMethod = createEngineGeneric.MakeGenericMethod(dynamicType, typeof(ModelOutput));
object[] args = new object[]{ model , true, inputSchema, outputSchema };
var engine = genericMethod.Invoke(mlContext.Model, args);
Upvotes: 2
Views: 158
Reputation: 21709
You don't need to modify the method at all. What you need to do is take the Type
returned and use that to create a generic method using MakeGenericMethod that you can then invoke.
var dynamicType = DynamicType.CreateDynamicType(properties);
var method = mlContext.Model.GetType().GetMethod("CreatePredictionEngine");
System.Type type2 = ...; // however/wherever you get this instance of Type from
var generic = method.MakeGenericMethod(dynamicType, type2);
var param1 = ...; // however/wherever this comes from
var param2 = ...; // however/wherever this comes from
generic.Invoke(mlContext.Model, param1, param2);
That the type is generated dynamically by your CreateDynamicType
method is not relevant. You can use any instance of System.Type from any source as the instance on which you call MakeGenericMethod
.
Now to actually use your engine, assign it to a dynamic
. This allows you to dynamically call the Predict
method.
dynamic engine = (dynamic)genericMethod.Invoke(mlContext.Model, args);
var dest = engine.Predict(source)
Alternatively, you're right, you could create another generic method for Predict
and invoke that with reflection.
Without knowing the rest of your code, you'll have to continue dynamic calls using dest
until you're done with it, unless you can get back to a point where you can then call normal type safe C#.
Upvotes: 3