5

次のようなクラスがあるとしましょう。

public class AcceptMethods
{
    public int Accept(string s, int k = 1)
    {
        return 1;
    }

    public int Accept(object s)
    {
        return 2;
    }

    public int Accept(IEnumerable<object> s)
    {
        return 7;
    }
    public int Accept(IList<object> s)
    {
        return 4;
    }
}

これをコードで使用しようとすると、次のようなものを使用します。

        object[] list = new object[] { "a", new object[0], "c", "d" };
        Assert.AreEqual(7, list.Select((a)=>((int)new AcceptMethods().Accept((dynamic)a))).Sum());

7 である理由は、オーバーロードの解決が [ IList<object>]IEnumerable<object>と [ ] よりも [ ] を優先し、 object[ string, ] が [ ] よりも優先されるためです。int=defaultobject

私のシナリオでは、リフレクションを使用して最適なオーバーロードを取得したいと考えています。つまり、「最高」は「c# オーバーロード解決」として定義されます。例えば:

int sum = 0;
foreach (var item in list)
{
    var method = GetBestMatching(typeof(AcceptMethods).GetMethods(), item.GetType());
    sum += (int)method.Invoke(myObject, new object[]{item});
}
Assert.AreEqual(7, sum);

私がスケッチしたシナリオにはパラメーターが 1 つしかありませんが、私が求めるソリューションには複数のパラメーターが含まれる場合があります。

更新 1 :

過負荷解決の実装の難しさから SO には難しすぎるというコメントをいただいたので (私も十分承知しています)、アップデートを送信したいと思います。私の主張に力を与えるために、これが私の最初の試みでした。これは、オーバーロードの解決を処理するデフォルトの .NET バインダーを使用します。

    private MethodBase GetBestMatching(IEnumerable<MethodInfo> methods, Type[] parameters)
    {
        return Type.DefaultBinder.SelectMethod(BindingFlags.Instance | BindingFlags.Public | BindingFlags.OptionalParamBinding | BindingFlags.InvokeMethod,
                        methods.ToArray(), parameters, null);
    }

このバージョンは、すでに単純なオーバーロードの解決を正しく行っているようですが、オプションのパラメーターでは機能しません。ここで示したように、.NET は型バインディングで機能するので、このソリューションはかなり簡単に実装できると思います。

4

2 に答える 2

4

これは大規模なテーマであり、非常に多くの作業が必要であり、私の意見では SO の回答にまとめることはできません。C# の仕様を読み、オーバーロードの解決を定義する正式なルールを読み (ジェネリック メソッドにも注意してください)、ニーズを満たすところまでそれらを実装することをお勧めします。

アップデート

オプション (つまり、デフォルト値を持つパラメーター) は些細なケースではありません。また、Reflection バインダーはそれらを埋めようとはしません。これは、デフォルト値を識別し、それらを引き出して、呼び出しに挿入するのがコンパイラの仕事であるためです。そのような方法。

次のようなマルチパス アプローチが必要です (注 - ジェネリックは含まれません)。

  1. パラメーターの数とそれらのパラメーターの型が、取得した引数の数と型と正確に一致するメソッドを手動で検索します。一致するものが見つかったら、それを使用して寝かせます。

  2. 次に、オーバーロードを選択するためのメソッドの「候補リスト」を特定します (通常、それは名前によるものです。ジェネリックもここで除外する場合があります。それらもバインドしようとする場合を除きます)。

  3. これらのメソッドのいずれにもオプションのパラメーターがない場合は、質問に従ってデフォルトのバインダーを使用して一致を見つけることができる場合があります (そうでない場合は、タイプに基づく引数/パラメーター ランキング アルゴリズムが必要です。 5) にスキップします。

  4. 3) で作成された候補リストを再実行し、すべてのオプションのパラメーターを取り出して、それらのデフォルト値を独自のパラメーター リストに組み込みます (この時点で、メソッドごとに個別のパラメーター セットを作成する必要がある場合があります。提供されていますが、デフォルトも含まれています)。

  5. 3) および場合によっては 4) に組み込まれているこれらのメソッドのランキング アルゴリズムを実行して、最適な一致を特定します (これについてはよく理解しているようですので、ここではすべてを説明しません。単純なアルゴリズムではなく、私は率直に言って、ここでもすべてをそのまま引用することはできません!)。

  6. ランキング アルゴリズムは明確な勝利方法を生み出す必要があります。あなたが明確な勝者を得た場合、それはあなたがバインドするものです. そうしないと、あいまいさが生じ、あきらめなければなりません。

この時点で、私自身の SO に興味があるかもしれません -デフォルト パラメーターとリフレクション: ParameterInfo.IsOptional の場合、DefaultValue は常に信頼できますか? -これは、デフォルトのパラメーターを持つメソッドを特定し、それらを持ち上げる方法に役立ちます。

于 2013-01-14T09:00:48.557 に答える
2

ランタイム オーバーロードの解決を行いたい他の人にとって、これは実装方法に関するかなり完全な説明です。

重要な補足事項として、「動的」なトリックはすべてのシナリオ (特にジェネリック) で機能するわけではありません。コンパイラは実行時の動作よりも柔軟であるようです。

また、これは完全なアルゴリズム/実装ではありません (または少なくともそうではないと思います) が、それにもかかわらず、ほとんどの場合に機能することに注意してください。これは、配列の共分散などの困難なケースを含め、これまでに遭遇したすべてのケースで機能することがわかりました。

スコアリング アルゴリズムは次のように機能します。

  • パラメータ タイプ == ソース タイプの場合: スコア = 0
  • パラメーターが有効なジェネリック型引数である場合 (ジェネリック制約): スコア = 1
  • ソース タイプがパラメーター タイプに暗黙的に変換可能な場合: スコア = 2 (すべてのルールについては、http: //msdn.microsoft.com/en-us/library/y5b434w4.aspxを参照してください)
  • デフォルトのパラメーターを入力する必要がある場合: スコア = 3
  • それ以外の場合は、以下のように互換性スコアのスコアを計算します

互換性スコアは、型 A と型 B の間の最も厳密な変換です (共分散、反分散を含む)。たとえば、string[] は IList への 1 回の変換 (object[] の次に IList を使用) と IEnumerable への 2 回の変換 (1. object[] の次に IEnumerable を使用、または 2. IEnumerable を使用) を行います。したがって、IList はより厳密な変換であるため、選択されます。

コンバージョン数のカウントは簡単です。

            int cnt = CountCompatible(parameter.ParameterType, sourceType.GetInterfaces()) +
                      CountCompatible(parameter.ParameterType, sourceType.GetBaseTypes()) +
                      CountCompatible(parameter.ParameterType, new Type[] { sourceType });
            [...]

    private static int CountCompatible(Type dst, IEnumerable<Type> types)
    {
        int cnt = 0;
        foreach (var t in types)
        {
            if (dst.IsAssignableFrom(t))
            {
                ++cnt;
            }
        }
        return cnt;
    }

より厳密な変換が使用されたときに、より良いスコアが選択されるようにするために、スコアを「スコア = 5 - 1.0 / (cnt + 2);」として計算します。+2 は、0 または 1 で除算しないことを保証し、スコアが 4 ~ 5 になるようにします。

オーバーロードの解決を行うには、すべての引数のスコアが最小のメソッドを選択します。呼び出し時にデフォルトのメソッド引数を適切に入力してください (上記の Andras による優れたリンクを参照)。また、メソッドを返す前に、必ず汎用引数を入力してください。最適な方法で引き分けが発生した場合: 最善の解決策は、例外をスローすることです。

ご参考までに、はい...すべてを正しく機能させるにはかなりの作業が必要です...それが完了したら、フレームワークで動作するバージョンを利用できるようにする予定です。(私のプロフィールに有効なウェブサイト リンクが表示された瞬間にわかるでしょう :-) )

于 2013-01-15T11:17:07.707 に答える