2015-10-26 12 views
24

これを明確にするために、私は動的とMakeGenericTypeを使って作業しています。しかし、私は助けができませんが、これを行う良い方法があると思います。私がしようとしているのは、Unityを使用して「プラグイン」ローダーを作成することです。私はあなたが私がやっていることに対する感覚を得ることができるように、コードを投稿するときにそれを説明します。ジェネリック型のインスタンスを実行時に解決された関数に返す

まず私は、プラグイン自体投稿します。ここで注意すべき

[RegisterAction("MyPlugin", typeof(bool), typeof(MyPlugin))] 
public class MyPlugin: IStrategy<bool> 
{ 
    public IStrategyResult<bool> Execute(ISerializable info = null) 
    { 
     bool result; 
     try 
     { 
      // do stuff 
      result = true; 
     } 
     catch (Exception) 
     { 
      result = false; 
     } 

     return new StrategyResult<bool> 
     { 
      Value = result 
     }; 
    } 
} 

カップルの事を。その後

[AttributeUsage(AttributeTargets.Class)] 
public sealed class RegisterActionAttribute : Attribute 
{ 
    public StrategyAction StrategyAction { get; } 

    public RegisterActionAttribute(string actionName, Type targetType, Type returnType, params string[] depdencies) 
    { 
     StrategyAction = new StrategyAction 
     { 
      Name = actionName, 
      StrategyType = targetType, 
      ResponseType = returnType, 
      Dependencies = depdencies 
     }; 
    } 
} 

インタフェース:かなりまっすぐ進む

public interface IStrategy<T> 
{ 
    IStrategyResult<T> Execute(ISerializable info = null); 
} 

public interface IStrategyResult<T> 
{ 
    bool IsValid { get; set; } 
    T Value { get; set; } 
} 

すべての最初のRegisterActionAttributeです。ここでの目標は、クラスがロードされたときにクラスにメタデータを添付することです。ロードは、ファイル検索パターンを使用してbinディレクトリにアセンブリを単にロードするラッパーを使用して単一性を介して行われ、StrategyActionsのコレクションを持つシングルトンクラスに追加されます。私はそれが動作し、アセンブリを登録して解決することを知っているので、私はここにすべての統一コードをペーストする必要はありません。

今質問の肉に。私はアクションを実行するシングルトン上の関数を持っています。これらはUnity.Interception HandlerAttributesに適用し、そのような文字列を渡された(私は、このためのコードを投稿することができますが、私はそれが関連だったとは思いませんでした)されています

[ExecuteAction("MyPlugin")] 

ハンドラはシングルトン上で実行次の関数を呼び出しますクラスが登録されている(コレクションに追加されている)関数を「実行する」。

public dynamic Execute(string action, params object[] parameters) 
{ 
    var strategyAction = _registeredActions.FirstOrDefault(a => a.Name == action); 
    if (strategyAction == null) 
     return null; 

    var type = typeof (IStrategy<>); 
    var generic = type.MakeGenericType(strategyAction.StrategyType); 

    var returnType = typeof (IStrategyResult<>); 
    var genericReturn = returnType.MakeGenericType(strategyAction.ResponseType); 

    var instance = UnityManager.Container.Resolve(generic, strategyAction.Name); 
    var method = instance.GetType().GetMethod("Execute"); 

    return method.Invoke(instance, parameters); 
} 

この実行は、依存関係やものではありません(下記参照)を管理するためのソート結果のコレクションを返します列挙子コールに包まれています。これらの値は、ISTrategyResult {T}のValueプロパティを使用して呼び出し元によって参照され、他のビジネスルールで定義されたさまざまな処理を行います。

public List<dynamic> ExecuteQueuedActions() 
    { 
     var results = new List<dynamic>(); 
     var actions = _queuedActions.AsQueryable(); 
     var sortedActions = TopologicalSort.Sort(actions, action => action.Dependencies, action => action.Name); 
     foreach(var strategyAction in sortedActions) 
     { 
      _queuedActions.Remove(strategyAction); 
      results.Add(Execute(strategyAction.Name)); 
     } 
     return results; 
    } 

これはうまくいきましたが、これは機能し、プラグインのRegisterAction属性で指定された戻り値の型を取得します。ご覧のとおり、プラグインのタイプと戻り値の型をキャプチャしています。私は "ジェネリック"変数を使用して、MakeGenericTypeを使用して型を一致させて解決します。これは正常に動作します。また、コレクションの型に基づいて戻り値の型を表すジェネリックを作成しています。

ここで私が気に入らないのは、この値を関数に返すためにdynamicを使用することです。私はIStrategyResult {T}としてこれを返す方法を理解できません。なぜなら、明らかに "動的Execute(...")呼び出し側は実行時に関数の戻り値の型を意味することができないからです。 MakeGenericMethodコールでExecuteを呼び出すのは、実際に期待されるタイプのStrategyActionを持っているからです。コール中にTタイプを決定している間にIStrategyResult {T}の厳密な型付けされた結果を返す方法を見つけることができれば、 。

なぜ私は現在の実装でこれを行うことができないのか理解していますが、私はダイナミックを使わずにこの機能をすべてラップする方法を見つけようとしており、役に立つかもしれないと思っています。これを非ジェネリックなクラスやそれ以外の呼び出しでラップすると、それが唯一の解決策であれば問題ありません。

+1

あなたは一度それを持っていると結果はどうなりますか?結果を扱うための対応する 'IHandler 'インタフェースが必要なようです。それで 'Execute'と' Handle'メソッドを 'Action'として構成するだけです。 – Lee

+0

私は呼び出しの結果を集約するために使用できるラッパー関数を追加しました。うまくいけばあなたの質問に答えます。あるいは、私はあなたの反応を誤解していますか? – Brandon

+0

私はあなたが言っていることを見ていると思います。しかし、私はこれらの値のいくつかを返す必要があることを考えると、私は呼び出し元のTの型を知っているのと同じ問題に遭遇すると思います。私はこれがこのようにすることに固有の問題であることを知っています。私はそれが獣の本質だと思うし、私ができる唯一のことは、結果を適切に処理するために呼び出し側にチェック/キャストを追加することだけです。ダイナミックについての涼しい(しかし危険な)ことは、結果を参照するためにキャストする必要がないということです。しかし、かつては哲学的に "灰色の"領域でダイナミックなものでした。 – Brandon

答えて

7

プラグインを呼び出す方法を理解するだけでなく、より多くのリファクタリングが必要です。

[RegisterAction]属性にtargetTypeとreturnTypeを保持する必要はありません。属性へのこれらのパラメータは、コードと簡単に同期しなくなる可能性があり、潜在的な穴になります。それはが本当には一般的なことを持っているか、のタイプをカプセル化することができ、特定の方法があります - あなたはあなたのIStrategyResult<>で行うのですかどのようなデータを、消費しない方法:

は、その後、あなたのセットアップの他の側面から考えます結果?私は、ホストに何かを返すプラグインシステムを想像することはできません。ヒントは実際にあなたのdynamic Execute(...)にあります - あなたのパラメータとあなたの結果は強いタイピングを失い、プラグインが何かを助けていないことを強く示しています。ただ、objectかを使用する - より良い - 代わりに、現在のインターフェースのStrategyResultクラスを作成し、そこに必要なものは何でもプロパティを提供(私はいくつかの軽薄な例を追加しました)、のような:次に

public class StrategyResult{ 
    public object Result{get;set;} 
    public Type ResultType {get;set;} 

    // frivolous examples 
    public bool IsError {get;set;} 
    public string ErrorMessage {get;set;} 

    // really off-the-wall example 
    public Func<StrategyHostContext,bool> ApplyResultToContext {get;set;} 

    public StrategyResult(){ 
    } 

    public StrategyResult FromStrategy(IStrategy strategy){ 
    return new StrategyResult{ 
     ResultType = strategy.ResultType 
    } 
    } 

    public StrategyResult FromStrategyExecute(IStrategy strategy, ISerializable info = null){ 
    var result = FromStrategy(strategy); 
    try{ 
     strategy.Execute(info); 
    } catch (Exception x){ 
     result.IsError = true; 
     result.ErrorMessage = x.Message; 
    } 
    } 
} 

あなたIStrategyは、次のようになります。

public interface IStrategy{ 
    Type ResultType {get;} 
    void Initialize(SomeContextClassMaybe context); 
    StrategyResult Execute(ISerializable info = null); 
} 

また、大規模なプラグインをロードするためにそれをより効率的にするために、あなたの属性を変更することができます。

[AttributeUsage(AttributeTargets.Assembly)] 
public sealed class AddinStrategyAttribute : Attribute 
{ 
    public Type StategyType {get; private set;} 
    public AddinStrategyAttribute(Type strategyType){ 
    StrategyType = strategyType; 
    } 
} 

...そしてそのような属性を使用します。

[assembly:AddinStrategy(typeof(BoolStrategy))] // note it's outside the namespace 
namespace MyNamespace{ 
    public class BoolStrategy: IStrategy{ 
     public Type ResultType { get{ return typeof(bool);}} 
     public void Initialize (SomeContextClassMaybe context){ 
     } 
     public StrategyResult Execute(ISerializable info = null){ 
     return StrategyResult.FromStrategyExecute(this,info); 
     } 
    } 
} 
+0

皆様のご返信に遅れて申し訳ありませんが、私はちょっと別のプロジェクトに取り掛かりました。今度はやっとこれに戻ります。 – Brandon

+0

プラグインの厳密な型定義は、処理速度を上げるための「ヒント」として意図されていました。私は条件付きブロック(コアに変更を加えることなくプラグインの拡張性を制限する)や呼び出し元のリフレクション(プラグイン呼び出しのボリュームを考慮して遅くなる可能性があるこのプラグインシステムがうまくいかなければならない場合は、コレクションを列挙するときにこれらのものを何百から何千回呼び出すことになる可能性があります。 – Brandon

+0

両方を達成するには(プラグインの中にメソッドとメソッドを追加する)コンテキストデータに自身のデータを貼り付ける--ApplicationResultToContextはその目的のためのものです –

2

RegisterActionAttributeコンストラクタにreturnType引数を渡して、このpickleに入りました。1つのExecute()メソッドしかないので、戻り値の型が異なることがあるという事実に対処する必要があります。

dynamicを使用すると、ほぼ同じくらいです。 Execute()を汎用にすることはできますが、型パラメータと属性のResponseTypeの不一致に対処する必要があります。コンパイラが捕捉できるものではなく、これは実行時に失敗します。これは一般的ではありません。

率直に言えば、これはあまりにも多くの柔軟性のように思えます。戻り値の型が間違っている点を解釈する危険性がある場合、「登録アクション」の結果はむしろ真実です。それは働いたか、うまくいかなかった。そして実際にあなたが実装した方法です。最初のプラグインスニペットはboolを返します。

非常に高い確率でboolを使用しないでください。失敗は強打をしなければならない、あなたは例外を投げるだろう。

2

このようなスーパーインタフェースIStrategyResult定義していない理由:

public IStrategyResult Execute(string action, params object[] parameters)

そして、あなたのStrategyResult : IStrategyResult<T>クラスがtypeof(T)

を返すようにプロパティを設定している:

interface IStrategyResult 
{ 
    Type ReturnType { get; } 
} 

interface IStrategyResult<T> : IStrategyResult 
{ 
    // your code here 
} 

を次に、あなたがこのように実行定義は、

慣例により、(またはen abstract StrategyResult<T> : IStrategyResult<T>クラスで継承を使用して強制的に)Tは、非汎用IStrategyResultインターフェイスのReturnTypeプロパティと同じにする必要があります。

5

ExecuteActionsの呼び出し側がプラグインや結果のいずれかにTについての知識を持っていないし、とにかくdynamicまたはobjectと協力しなければならないと仮定するとし、その後、次のように動作することがあります。

インフラ:

public interface IStrategy 
{ 
    IStrategyResult Execute(ISerializable info = null); 
} 

public interface IStrategyResult 
{ 
    bool IsValid { get; } 
    dynamic Value { get; } 
} 

public class StrategyResult<T> : IStrategyResult 
{ 
    public T Value { get; private set; } 
    public StrategyResult(T value) { this.Value = value; } 

    public bool IsValid { get { throw new NotImplementedException(); } } 

    dynamic IStrategyResult.Value { get { return this.Value; } } 

} 

[AttributeUsage(AttributeTargets.Class)] 
public sealed class RegisterActionAttribute : Attribute 
{ 
    public List<string> Dependencies { get; private set; } 

    public RegisterActionAttribute(params string[] depdencies) 
    { 
     this.Dependencies = new List<string>(depdencies); 
    } 
} 

public class StrategyAction 
{ 
    public string Name; 
    public List<string> Dependencies; 
} 

public abstract class BasePlugin<T> : IStrategy 
{ 
    public IStrategyResult Execute(ISerializable info = null) 
    { 
     return new StrategyResult<T>(this.execute(info)); 
    } 
    protected abstract T execute(ISerializable info); 
} 

例プラグイン:

[RegisterAction] 
public class MyFirstPlugin: BasePlugin<bool> 
{ 
    protected override bool execute(ISerializable info = null) 
    { 
     try 
     { 
      // do stuff 
      return true; 
     } 
     catch (Exception) 
     { 
      return false; 
     } 
    } 
} 

[RegisterAction("MyFirstPlugin")] 
public class MySecondPlugin: BasePlugin<string> 
{ 
    protected override string execute(ISerializable info = null) 
    { 
     try 
     { 
      // do stuff 
      return "success"; 
     } 
     catch (Exception) 
     { 
      return "failed"; 
     } 
    } 
} 

例実行エンジン:

public class Engine 
{ 

    public List<StrategyAction> registeredActions = new List<StrategyAction>(); 
    private List<StrategyAction> queuedActions  = new List<StrategyAction>(); 

    public IStrategyResult Execute(string action, ISerializable info = null) 
    { 
     if (this.registeredActions.FirstOrDefault(a=>a.Name == action) == null) return null; 

     // This code did not appear to be used anyway 
     //var returnType = typeof (IStrategyResult<>);            //var genericReturn = returnType.MakeGenericType(strategyAction.ResponseType); 

     var instance = (IStrategy) UnityManager.Container.Resolve(typeof(IStrategy), action); 

     return instance.Execute(info); 
    } 

    public List<IStrategyResult> ExecuteQueuedActions() 
    { 
     var results   = new List<IStrategyResult>(); 
     var actions   = this.queuedActions.AsQueryable(); 
     var sortedActions = TopologicalSort.Sort(actions, action => action.Dependencies, action => action.Name); 
     foreach(var strategyAction in sortedActions) 
     { 
      this.queuedActions.Remove(strategyAction); 
      results.Add(Execute(strategyAction.Name)); 
     } 
     return results; 
    } 

} 

注プラグインがロードされるとき、必要ロードプラグインタイプの名前とともにRegisterActionAttribute情報はStrategyActionインスタンスに合成することとエンジンのregisteredActionsフィールドにロードされます。

プラグインは強力なタイプで動作することができますが、エンジンではさまざまな種類の処理が可能です。より厳密に型指定されたデータを処理するエンジンが必要な場合は、ExecuteQueuedActionsの呼び出し元がExecuteQueuedActionsの結果をどのように使用するかの例を示してください。

関連する問題