11

問題

ソースコードにアクセスできない既存のライブラリを使用しています。このライブラリは AST を表します。

この AST の一部をコピーしたいのですが、その過程で変数への参照の名前を変更します。Expression オブジェクトを保持する AssignCommand オブジェクトが存在する可能性があるため、各オブジェクトを独自の関数でコピーできるようにして、それらを再帰的に呼び出すことができるようにしたいと考えています。ただし、ライブラリのコードにアクセスできないため、 などのメソッドを追加することはできませんCopyAndRename(string prefix)

したがって、私のアプローチは、いくつかのオーバーロードを持つ単一の関数を作成することでしたRename。したがって、次のような家族機能があります。

public static Command Rename(Command cmd, string prefix)
public static AssignCommand Rename(AssignCommand cmd, string prefix)
public static AdditionExpressionRename(AdditionExpression expr, string prefix)
....

関数は で構成されます。List<Command>ここで、AssignCommandは のサブクラスですCommandCommand-function に aを渡すだけRenameで、ランタイムが最も具体的なものを見つけると仮定しました。ただし、そうではなく、すべてのコマンドが に渡されCommand Rename(Command cmd, string prefix)ます。これはなぜですか?醜い操作を使用せずに、呼び出しを正しい関数に委任する方法はありisますか?

最小限の例

この問題を次の NUnit-Testcode に分解しました

using NUnit.Framework;

public class TopClass{
    public int retVal;
}

public class SubClassA : TopClass{ }

[TestFixture]
public class ThrowawayTest {


    private TopClass Foo (TopClass x) {
        x.retVal = 1;
        return x;
    }

    private SubClassA Foo (SubClassA x) {
        x.retVal = 2;
        return x;
    }

    [Test]
    public void OverloadTest(){
        TopClass t = new TopClass();
        TopClass t1 = new SubClassA();
        SubClassA s1 = new SubClassA();

    t = Foo (t);
        t1 = Foo (t1);
        s1 = Foo (s1);

        Assert.AreEqual(1, t.retVal);
        Assert.AreEqual(2, s1.retVal);
        Assert.AreEqual(2, t1.retVal);
    }
}

つまり、私の質問は次のようにis要約できます。

拡張方法

また、次のように拡張メソッドを使用してみました。上記のアプローチの構文糖衣にすぎないため、これは問題を解決しませんでした。

using NUnit.Framework;
using ExtensionMethods;

public class TopClass{
    public int retVal;
}

public class SubClassA : TopClass{ }

[TestFixture]
public class ThrowawayTest {


    private TopClass Foo (TopClass x) {
        x.retVal = 1;
        return x;
    }

    private SubClassA Foo (SubClassA x) {
        x.retVal = 2;
        return x;
    }

    [Test]
    public void OverloadTest(){
        TopClass t = new TopClass();
        TopClass t1 = new SubClassA();
        SubClassA s1 = new SubClassA();

        t.Foo(); s1.Foo(); t1.Foo();

        Assert.AreEqual(1, t.retVal);
        Assert.AreEqual(2, s1.retVal);
        Assert.AreEqual(2, t1.retVal);
    }
}

namespace ExtensionMethods{
    public static class Extensions {
        public static void Foo (this TopClass x) {
            x.retVal = 1;
        }

        public static void Foo (this SubClassA x) {
            x.retVal = 2;
        }
    }
}
4

3 に答える 3

7

ケビンの答えと同様に、dynamicキーワードを利用することを検討します。2 つの追加のアプローチについて説明します。

ここで、ソース コードにアクセスする必要はありません。型自体、つまりアセンブリにアクセスする必要があるだけです。publicタイプが(privateまたはでない)である限り、internalこれらは機能するはずです。

動的訪問者

これは、従来のVisitorパターンと同様のアプローチを使用します。

Commandパラメータとして外部オブジェクトを受け取り、サブタイプごとに 1 つのメソッドを持つビジター オブジェクトを作成します (エンド タイプであり、 のような中間クラスまたは基本クラスではありません)。

次に、コンパイル時に正確なタイプがわからない特定のオブジェクトでそれを呼び出すには、次のようにビジターを実行します。

visitor.Visit((dynamic)target);

訪問したいサブ式を持つ型については、ビジター自体の中で再帰を処理することもできます。

ハンドラのディクショナリ

ここで、すべてではなく、いくつかのタイプのみを処理したい場合は、Dictionaryによってインデックス付けされたハンドラーの を作成する方が簡単かもしれませんType。そうすれば、辞書に正確なタイプのハンドラーがあるかどうかを確認できます。ある場合は、それを呼び出します。ハンドラー内で強制的にキャストする可能性のある標準の呼び出しを使用するか、DLR 呼び出しを使用しますが、パフォーマンスが少し低下します)。

于 2012-12-31T17:45:02.210 に答える
4

dynamicMono でサポートされているかどうかはわかりませんが、ジェネリックとC# 4.0のキーワードを非常に具体的に使用することで、探しているものを実現できます。あなたがしようとしているのは、新しい仮想スロットを作成することですが、セマンティクスが少し異なります (C# 仮想関数は共変ではありません)。仮想関数とdynamic同じように、関数のオーバーロード解決をランタイムにプッシュします (ただし、効率ははるかに低くなります)。拡張メソッドと静的関数の両方にコンパイル時のオーバーロード解決があるため、変数の静的型が使用されるものであり、これがあなたが戦っている問題です。

public class FooBase
{
    public int RetVal { get; set; }
}

public class Bar : FooBase {}

動的訪問者の設定。

public class RetValDynamicVisitor
{
    public const int FooVal = 1;
    public const int BarVal = 2;

    public T Visit<T>(T inputObj) where T : class
    {            
        // Force dynamic type of inputObj
        dynamic @dynamic = inputObj; 

        // SetRetVal is now bound at runtime, not at compile time
        return SetRetVal(@dynamic);
    }

    private FooBase SetRetVal(FooBase fooBase)
    {
        fooBase.RetVal = FooVal;
        return fooBase;
    }

    private Bar SetRetVal(Bar bar)
    {
        bar.RetVal = BarVal;
        return bar;
    }
}

特に興味深いのはinputObj, @dynamicin Visit<T>forの型ですVisit(new Bar())

public class RetValDynamicVisitorTests
{
    private readonly RetValDynamicVisitor _sut = new RetValDynamicVisitor();

    [Fact]
    public void VisitTest()
    {
        FooBase fooBase = _sut.Visit(new FooBase());
        FooBase barAsFooBase = _sut.Visit(new Bar() as FooBase);
        Bar bar = _sut.Visit(new Bar());

        Assert.Equal(RetValDynamicVisitor.FooVal, fooBase.RetVal);
        Assert.Equal(RetValDynamicVisitor.BarVal, barAsFooBase.RetVal);
        Assert.Equal(RetValDynamicVisitor.BarVal, bar.RetVal);
    }
}

Monoでそれが実現可能であることを願っています!

于 2012-12-30T17:49:24.747 に答える
1

これは動的でないバージョンです。動的バージョンは遅すぎます (最初の呼び出し):

public static class Visitor
{
    /// <summary>
    /// Create <see cref="IActionVisitor{TBase}"/>.
    /// </summary>
    /// <typeparam name="TBase">Base type.</typeparam>
    /// <returns>New instance of <see cref="IActionVisitor{TBase}"/>.</returns>
    public static IActionVisitor<TBase> For<TBase>()
        where TBase : class
    {
        return new ActionVisitor<TBase>();
    }

    private sealed class ActionVisitor<TBase> : IActionVisitor<TBase>
        where TBase : class
    {
        private readonly Dictionary<Type, Action<TBase>> _repository =
            new Dictionary<Type, Action<TBase>>();

        public void Register<T>(Action<T> action)
            where T : TBase
        {
            _repository[typeof(T)] = x => action((T)x);
        }

        public void Visit<T>(T value)
            where T : TBase

        {
            Action<TBase> action = _repository[value.GetType()];
            action(value);
        }
    }
}

インターフェイス宣言:

public interface IActionVisitor<in TBase>
    where TBase : class
{

    void Register<T>(Action<T> action)
        where T : TBase;    

    void Visit<T>(T value)
        where T : TBase;
}

使用法:

IActionVisitor<Letter> visitor = Visitor.For<Letter>();
visitor.Register<A>(x => Console.WriteLine(x.GetType().Name));
visitor.Register<B>(x => Console.WriteLine(x.GetType().Name));

Letter a = new A();
Letter b = new B();
visitor.Visit(a);
visitor.Visit(b);

コンソール出力: A、B、詳しくはこちらをご覧ください

于 2013-05-06T20:56:04.550 に答える