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);