【问题标题】:Explicit Interface And Generic / Dynamic Type Conversion显式接口和通用/动态类型转换
【发布时间】:2015-08-27 12:55:14
【问题描述】:

在我陈述我的问题之前,让我提供一些关于我正在尝试做的事情的背景信息,因为也许有更好的方法来实现它。

我有一个类C,它继承了两个接口AB

public interface A
{
    void DoStuff();
}

public interface B
{
    void DoStuff();
}

C 为DoStuff 实现了两个不同的主体。

void A.DoStuff()
{
    Console.WriteLine("A");
}

void B.DoStuff()
{
    Console.WriteLine("B");
}

我当然知道DoStuff 可以像下面这样调用。

var c = new C();
var a = (A)c;
a.DoStuff();
var b = (B)c;
b.DoStuff();

我想要做的是避免将C 转换为AB,然后在AB 上调用DoStuff

所以我认为我可以实现一个通用方法来为我做这件事,所以我首先尝试这样。

public void DoStuff<T>()
{
    var i = (T)this;
    i.DoStuff();
}

当然,考虑到您可以将this 转换为T,这将导致编译器错误,因此我的下一步是尝试创建一个传递this 实例的静态方法,然后在那里进行了转换。

private static To ConvertToDoStuffInterface<From, To>(From from)
{
    return (To)from;
}

我没想到它会起作用,但值得一试。正如我所料,它会导致编译器错误,因为From 无法显式转换为To

我最后的手段是使用dynamic 关键字,当然它顺利通过ConvertToDoStuffInterface,但是一旦我尝试调用DoStuff,它就会抛出一个RuntimeBinderException,我有点怀疑它可能会。我知道为什么会抛出异常,这当然是因为它无法将ConvertToDoStuffInterface 的结果正确绑定到任何类型,并且我认为需要进行某种类型的转换。

这是dynamic 用法的代码。

private static dynamic ConvertToDoStuffInterface<T>(dynamic from)
{
    return (T)from;
}

public void DoStuff<T>()
{
    var i = ConvertToDoStuffInterface<T>(this);
    i.DoStuff();
}

我的问题是,如果没有更好的方法可以使用泛型或dynamic 来实现这一点,我将如何进行从dynamicT 的转换?有没有一种方法可以检查传递给DoStuff&lt;T&gt; 的类型是否具有DoStuff 方法,然后允许我最初尝试的方法?

为了澄清我想要做的事情是这样的

var c = new C();
c.DoStuff<A>(); // Calls A.DoStuff()
c.DoStuff<B>(); // Calls B.DoStuff()

这是我的C 实现。

public class C : A, B
{        
    public void DoStuff<T>()
    {
        // ... DoStuff<T> implementation here ...
    }

    void A.DoStuff()
    {
        Console.WriteLine("A");
    }

    void B.DoStuff()
    {
        Console.WriteLine("B");
    }
}

【问题讨论】:

  • @DavidE 这是一个不好的做法。您永远不应该在 as 关键字之后尊重实例。它可能为空。您需要在那里进行空检查。更干净的方法是使用直接投射。
  • 我不明白c.DoStuff&lt;A&gt; 怎么比((A)c).DoStuff 干净。在这两种情况下,您都必须指定要调度的接口。
  • 确实,感谢您指出 Sriram! (为将来的读者提供上下文 - 我删除了之前建议 (A as C).DoStuff() 的评论,建议不好)
  • 虽然这是真的李,但这样做的目的并不是真正的可读性。

标签: c# generics dynamic explicit-interface


【解决方案1】:

你可以使用这两个InvokeAsextension 方法

public static void InvokeAs<T>(this object obj, Action<T> action)
{
    action((T) obj);
}

public static TResult InvokeAs<T, TResult>(this object obj, Func<T, TResult> func)
{
    return func((T) obj);
}

然后像这样使用:

var c = new C();

c.InvokeAs<A>(x => x.DoStuff());
c.InvokeAs<B>(x => x.DoStuff());

但正如 cmets 中所指出的,((B) c).DoStuff() 并没有那么糟糕。

【讨论】:

  • 这可能是最接近我想要的,但我想如果这是我会选择((B)c).DoStuff()的唯一方法
【解决方案2】:

要完全按照您的意愿进行行为,您可以使用反射:

class C : A, B
{
    void B.DoStuff()
    {
        Console.WriteLine("B");
    }

    void A.DoStuff()
    {
        Console.WriteLine("A");
    }

    public void DoStuff<T>() 
    {
        var mi = typeof(T).GetMethod("DoStuff");
        mi.Invoke(this, new object[] { });
    }
}

然后调用:

        var c = new C();
        c.DoStuff<A>();
        c.DoStuff<B>();
        Console.ReadLine();

按预期工作。 显然,如果“DoStuff”方法存在等等,您必须在 DoStuff 方法中进行适当的验证。

但是 - 我不知道用例 - 但对我来说似乎不是那么明显为什么这比强制转换更适合你。

【讨论】:

  • 是的,正如我所说,我不知道您的用例,它有效,并且可以优化,例如通过将方法分配给缓存的委托。
【解决方案3】:

我同意强制转换可能是最干净的(假设您无法修复接口中的名称冲突),但另一种方法是在 C 上创建两个实例方法来包装接口调用:

public class C : A, B
{        
    public void DoStuff<T>()
    {
        // ... DoStuff<T> implementation here ...
    }

    void A.DoStuff()
    {
        Console.WriteLine("A");
    }

    void B.DoStuff()
    {
        Console.WriteLine("B");
    }

    public void DoStuffAsA() { ((A)this).DoStuff(); }
    public void DoStuffAsB() { ((B)this).DoStuff(); }
}

至少它为您提供dynamic、反射和泛型所不具备的编译时安全性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-28
    • 1970-01-01
    • 1970-01-01
    • 2015-10-20
    • 2019-10-15
    • 1970-01-01
    相关资源
    最近更新 更多