【问题标题】:Passing generic parameter results in wrong overload being called传递泛型参数会导致调用错误的重载
【发布时间】:2012-02-14 14:59:52
【问题描述】:

我正在尝试更新 Medusa 以允许在其当前使用 List<DbParameter> 的任何地方使用装饰的 POCO。我遇到的问题是调用了错误的重载。这是我所看到的一个简单示例:

void Run()
{
    CallDoSomething<Program>("Hello World", new object());
    CallDoSomething<Program>("Hello World2", new List<int>());
}

// `DoSomething<T>` represents the functions that do the heavy lifting
public T DoSomething<T>(string someString, List<int> ints) where T : class
{
    Console.WriteLine("List<int>: {0}", someString);
    return default(T);
}
public T DoSomething<T>(string someString, object ints) where T : class
{
    Console.WriteLine("object: {0}", someString);
    // In my real implementation, this turns the object to a typed List<T>
    // and passes it to the previous overload.
    return default(T);
}

// We're trying to refactor the code in this method to reduce code duplication in
// the `CallDoSomething<T>` methods that will actually be called by the end user
internal T CallDoSomething<T, U>(string someString, U ints) where T : class
{
    // Do a bunch of stuff here that would otherwise be duplicated by the `CallDoSomething<T>` methods
    return DoSomething<T>(someString, ints);
}

public T CallDoSomething<T>(string someString, List<int> ints) where T : class
{
    return CallDoSomething<T, List<int>>(someString, ints);
}
public T CallDoSomething<T>(string someString, object ints) where T : class
{
    return CallDoSomething<T, object>(someString, ints);
}

在这种情况下,结果输出是:

object: Hello World
object: Hello World2

虽然我期待它是:

object: Hello World
List<int>: HelloWorld2

这两种情况都指向采用object 参数的重载是有道理的,因为它们都是对象。我怀疑这是因为(据我所知)泛型和重载解析是在编译时而不是运行时处理的。

我想到的第一个选择是使用反射在CallDoSomething&lt;T, U&gt; 中动态调用调用,但感觉太脏了。相反,我提出的解决方案涉及将委托传递给CallDoSomething&lt;T, U&gt;,这似乎可行。这是它的样子:

void Run()
{
    CallDoSomething<Program>("Hello World", new object());
    CallDoSomething<Program>("Hello World2", new List<int>());
}

public T DoSomething<T>(string someString, List<int> ints) where T : class
{
    Console.WriteLine("List<int>: {0}", someString);
    return default(T);
}
public T DoSomething<T>(string someString, object ints) where T : class
{
    Console.WriteLine("object: {0}", someString);
    return default(T);
}

internal delegate T DoSomethingDelegate<T, U>(string someString, U ints) where T : class;
internal T CallDoSomething<T, U>(string someString, U ints, DoSomethingDelegate<T, U> doSomething) where T : class
{
    // Do a bunch of stuff here that would otherwise be duplicated by the `CallDoSomething<T>` methods
    return doSomething(someString, ints);
}

public T CallDoSomething<T>(string someString, List<int> ints) where T : class
{
    return CallDoSomething<T, List<int>>(someString, ints, DoSomething<T>);
}
public T CallDoSomething<T>(string someString, object ints) where T : class
{
    return CallDoSomething<T, object>(someString, ints, DoSomething<T>);
}

这似乎有效,它消除了大量的代码重复,但它使代码相当复杂。有没有更好的方法来解决这个问题?

【问题讨论】:

    标签: c# oop


    【解决方案1】:

    是的,重载在编译时解决。如果您使用 C# 4,您可以强制在执行时对其进行评估,如下所示:

    internal T CallDoSomething<T, U>(string someString, U ints) where T : class
    {
        dynamic d = ints;
        return DoSomething<T>(someString, d);
    }
    

    但是,如果可能的话,我个人会尽量简化您的设计。这种事情很快就会变得混乱非常

    【讨论】:

    • 我在 .NET 4 中,所以 dynamic 闪过我的脑海,但我认为这不会有太大的不同,因为 ints 已经是通用的。不过这是有道理的。有什么简化设计的建议吗?
    • @M.Babcock:通用和动态是非常不同的。泛型是关于在编译时提供更多类型信息; dynamic 是关于在执行时使用更多类型信息。至于简化设计——如果不了解更多上下文就很难说。
    • 感谢您的帮助。我可能会坚持使用委托,只是为了允许在 .NET 4 之外使用。虽然委托解决方案似乎有效,但您知道这样做的任何原因吗?
    • @M.Babcock:只有简单。感觉应该有一个更简单的方法......
    猜你喜欢
    • 1970-01-01
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多