如何构建动态命令对象?

IAb*_*act 7 c# delegates dynamic .net-core

我会尽量说清楚。

  1. 插件使用反射和2个的架构AttributeS和一个抽象类:
    PluginEntryAttribute(Targets.Assembly, typeof(MyPlugin))
    PluginImplAttribute(Targets.Class, ...)
    abstract class Plugin
  2. 命令通过接口和委托路由到插件:
    例如:public delegate TTarget Command<TTarget>(object obj);
  3. 使用扩展方法 withCommand<>作为目标,CommandRouter在正确的目标接口上执行委托:
    例如:
public static TResult Execute<TTarget, TResult>(this Command<TTarget> target, Func<TTarget, TResult> func) {
     return CommandRouter.Default.Execute(func);
}
Run Code Online (Sandbox Code Playgroud)

把这些放在一起,我有一个用命令委托硬编码的类,如下所示:

public class Repositories {
     public static Command<IDispatchingRepository> Dispatching = (o) => { return (IDispatchingRepository)o; };
     public static Command<IPositioningRepository> Positioning = (o) => { return (IPositioningRepository)o; };
     public static Command<ISchedulingRepository> Scheduling = (o) => { return (ISchedulingRepository)o; };
     public static Command<IHistographyRepository> Histography = (o) => { return (IHistographyRepository)o; };
}
Run Code Online (Sandbox Code Playgroud)

当一个对象想要从存储库中查询时,实际执行看起来像这样:

var expBob = Dispatching.Execute(repo => repo.AddCustomer("Bob"));  
var actBob = Dispatching.Execute(repo => repo.GetCustomer("Bob"));  
Run Code Online (Sandbox Code Playgroud)

我的问题是:如何Repositories从插件动态创建这样的类?

我可以看到可能需要另一个属性的可能性。类似的东西:

[RoutedCommand("Dispatching", typeof(IDispatchingRepository)")]
public Command<IDispatchingRepository> Dispatching = (o) => { return (IDispatchingRepository)o; };
Run Code Online (Sandbox Code Playgroud)

这只是一个想法,但我不知道如何仍然创建类似类的动态菜单Repositories

为了完整起见,该CommandRouter.Execute(...)方法和相关的Dictionary<,>

private readonly Dictionary<Type, object> commandTargets;

internal TResult Execute<TTarget, TResult>(Func<TTarget, TResult> func) {
     var result = default(TResult);

     if (commandTargets.TryGetValue(typeof(TTarget), out object target)) {
          result = func((TTarget)target);
     }

     return result;
}
Run Code Online (Sandbox Code Playgroud)

vas*_*ski 1

好的,我不确定这是否是您要找的。我假设每个插件包含以下定义的字段:

public Command<T> {Name} = (o) => { return (T)o; };
Run Code Online (Sandbox Code Playgroud)

您提供的代码示例:

public Command<IDispatchingRepository> Dispatching = (o) => { return (IDispatchingRepository)o; };
Run Code Online (Sandbox Code Playgroud)

在 .NET Core 中动态创建类的一种方法是使用Microsoft.CodeAnalysis.CSharp nuget - 这就是 Roslyn。

结果是使用名为的类编译的程序集,DynamicRepositories将所有加载的 dll 中的所有插件的所有命令字段放入当前AppDomain表示为静态公共字段中。

该代码有 3 个主要组成部分:DynamicRepositoriesBuildInfo类、GetDynamicRepositoriesBuildInfo方法和LoadDynamicRepositortyIntoAppDomain方法。

DynamicRepositoriesBuildInfo- 来自插件的命令字段的信息以及动态并发症期间需要加载的所有程序集。Command这将是定义类型和类型的通用参数的程序集Command(例如IDispatchingRepository:)

GetDynamicRepositoriesBuildInfo方法 -通过扫描和DynamicRepositoriesBuildInfo的加载程序集使用反射进行创建。PluginEntryAttributePluginImplAttribute

LoadDynamicRepositortyIntoAppDomain方法 -DynamicRepositoriesBuildInfo它使用单个公共类创建名为 DynamicRepository.dll 的程序集App.Dynamic.DynamicRepositories

这是代码

public class DynamicRepositoriesBuildInfo
{
 public IReadOnlyCollection<Assembly> ReferencesAssemblies { get; }
    public IReadOnlyCollection<FieldInfo> PluginCommandFieldInfos { get; }

    public DynamicRepositoriesBuildInfo(
        IReadOnlyCollection<Assembly> referencesAssemblies,
        IReadOnlyCollection<FieldInfo> pluginCommandFieldInfos)
    {
        this.ReferencesAssemblies = referencesAssemblies;
        this.PluginCommandFieldInfos = pluginCommandFieldInfos;
    }
}


private static DynamicRepositoriesBuildInfo GetDynamicRepositoriesBuildInfo()
    {
    var pluginCommandProperties = (from a in AppDomain.CurrentDomain.GetAssemblies()
                                   let entryAttr = a.GetCustomAttribute<PluginEntryAttribute>()
                                   where entryAttr != null
                                   from t in a.DefinedTypes
                                   where t == entryAttr.PluginType
                                   from p in t.GetFields(BindingFlags.Public | BindingFlags.Instance)
                                   where p.FieldType.GetGenericTypeDefinition() == typeof(Command<>)
                                   select p).ToList();

    var referenceAssemblies = pluginCommandProperties
        .Select(x => x.DeclaringType.Assembly)
        .ToList();

    referenceAssemblies.AddRange(
        pluginCommandProperties
        .SelectMany(x => x.FieldType.GetGenericArguments())
        .Select(x => x.Assembly)
    );

    var buildInfo = new DynamicRepositoriesBuildInfo(
        pluginCommandFieldInfos: pluginCommandProperties,
        referencesAssemblies: referenceAssemblies.Distinct().ToList()
    );

    return buildInfo;
}

private static Assembly LoadDynamicRepositortyIntoAppDomain()
        {
            var buildInfo = GetDynamicRepositoriesBuildInfo();

            var csScriptBuilder = new StringBuilder();
            csScriptBuilder.AppendLine("using System;");
            csScriptBuilder.AppendLine("namespace App.Dynamic");
            csScriptBuilder.AppendLine("{");
            csScriptBuilder.AppendLine("    public class DynamicRepositories");
            csScriptBuilder.AppendLine("    {");
            foreach (var commandFieldInfo in buildInfo.PluginCommandFieldInfos)
            {
                var commandNamespaceStr = commandFieldInfo.FieldType.Namespace;
                var commandTypeStr = commandFieldInfo.FieldType.Name.Split('`')[0];
                var commandGenericArgStr = commandFieldInfo.FieldType.GetGenericArguments().Single().FullName;
                var commandFieldNameStr = commandFieldInfo.Name;

                csScriptBuilder.AppendLine($"public {commandNamespaceStr}.{commandTypeStr}<{commandGenericArgStr}> {commandFieldNameStr} => (o) => ({commandGenericArgStr})o;");
            }

            csScriptBuilder.AppendLine("    }");
            csScriptBuilder.AppendLine("}");

            var sourceText = SourceText.From(csScriptBuilder.ToString());
            var parseOpt = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3);
            var syntaxTree = SyntaxFactory.ParseSyntaxTree(sourceText, parseOpt);
            var references = new List<MetadataReference>
            {
                MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
                MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location),
            };

            references.AddRange(buildInfo.ReferencesAssemblies.Select(a => MetadataReference.CreateFromFile(a.Location)));

            var compileOpt = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
                    optimizationLevel: OptimizationLevel.Release,
                    assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default);

            var compilation = CSharpCompilation.Create(
                    "DynamicRepository.dll",
                    new[] { syntaxTree },
                    references: references,
                    options: compileOpt);

            using (var memStream = new MemoryStream())
            {
                var result = compilation.Emit(memStream);
                if (result.Success)
                {
                    var assembly = AppDomain.CurrentDomain.Load(memStream.ToArray());

                    return assembly;
                }
                else
                {
                    throw new ArgumentException();
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

这是代码的执行方式

var assembly = LoadDynamicRepositortyIntoAppDomain();
var type = assembly.GetType("App.Dynamic.DynamicRepositories");
Run Code Online (Sandbox Code Playgroud)

type变量表示编译后的类,该类将所有插件命令作为公共静态字段。一旦开始使用动态代码编译/构建,您就会失去所有类型安全性。如果您需要从type变量执行一些代码,则需要反射。

所以如果你有

PluginA 
{
  public Command<IDispatchingRepository> Dispatching= (o) => ....
}

PluginB 
{
   public Command<IDispatchingRepository> Scheduling = (o) => ....
}
Run Code Online (Sandbox Code Playgroud)

动态创建类型将如下所示

public class DynamicRepositories 
{
    public static Command<IDispatchingRepository> Dispatching= (o) => ....
    public static Command<IDispatchingRepository> Scheduling = (o) => ....
}
Run Code Online (Sandbox Code Playgroud)