为什么不会发生重载?

5

I have the following class:

class CrmToRealTypeConverter : IConverter
{
    #region IConverter Members

    public object Convert<T>(T obj)
    {
        return Convert(obj);
    }

    #endregion

    private DateTime? Convert(CrmDateTime obj)
    {
        return obj.IsNull == false ? (DateTime?)obj.UserTime : null;
    }

    private int? Convert(CrmNumber obj)
    {
        return obj.IsNull == false ? (int?)obj.Value : null;
    }

    private decimal? Convert(CrmDecimal obj)
    {
        return obj.IsNull == false ? (decimal?)obj.Value : null;
    }

    private double? Convert(CrmDouble obj)
    {
        return obj.IsNull == false ? (double?)obj.Value : null;
    }

    private float? Convert(CrmFloat obj)
    {
        return obj.IsNull == false ? (float?)obj.Value : null;
    }

    private decimal? Convert(CrmMoney obj)
    {
        return obj.IsNull == false ? (decimal?)obj.Value : null;
    }

    private bool? Convert(CrmBoolean obj)
    {
        return obj.IsNull == false ? (bool?)obj.Value : null;
    }
}

我正在尝试使用具体类型来专门化Convert方法。
目前,它只是在Convert<T>()中递归循环,直到发生堆栈溢出。

4个回答

3

多态不适用于方法调用的参数。一个可以使用的方法是检查obj的类型,将其强制转换为特定的类型,然后调用适当的重载。

public object Convert(object obj)
{
    if (obj is CrmDateTime)
        return Convert((CrmDateTime)obj);
    if (obj is CrmNumber)
        return Convert((CrmNumber)obj);
    // ...
}

这是C#最佳实践吗?同时编译器报错:错误2 无法将类型“T”转换为“Microsoft.Crm.Sdk.CrmDateTime” C:\Documents and Settings\omerk\My Documents\Visual Studio 2008\Projects\Agile.CRMActiveRecord\Agile.CRMActiveRecord\CrmToRealTypeConverter.cs 14 28 Agile.CRMActiveRecord - the_drow
@the_drow:你的Crm*类有一个共同的基类型(除了object以外)吗?我认为最好的做法是在基类中添加Convert作为虚拟(可能是抽象)方法,并在每个类中重写它。当然,如果这些类不是由您创建的,则无法实现此操作。 - Mark Byers
我并没有创建那些类,而且它们由于糟糕的设计没有共同的基类。 - the_drow
那就使用扩展方法吧。请看下面我的回答。 - Frédéric Hamidi

2

晚期绑定并不是你想象的那样进行的;编译器将Convert(obj)调用绑定到public object Convert<T>(T obj)方法中的相同方法(递归调用)。你似乎期望的行为是CLR会在运行时动态选择最合适的重载来执行,但它并不是这样工作的。相反,尝试使用以下代码:

public object Convert<T>(T obj)
{
   if (obj == null)
       throw new ArgumentNullException("obj");

    var cdt = obj as CrmDateTime;   
    if (cdt != null)
        return Convert(cdt); // bound at compile-time to DateTime? Convert(CrmDateTime)

    var cn = obj as CrmNumber;    
    if (cn != null)
        return Convert(cn); // bound at compile-time to int? Convert(CrmNumber)

    // ...    

    throw new NotSupportedException("Cannot convert " + obj.GetType());
}

如果您愿意,您可以在这里使用反射。这样的解决方案看起来会像这样:

// Making the method generic doesn't really help
public object Convert(object obj) 
{
   if (obj == null)
       throw new ArgumentNullException("obj");

    // Target method is always a private, instance method
    var bFlags = BindingFlags.Instance | BindingFlags.NonPublic;

    // ..which takes a parameter of the obj's type.      
    var parameterTypes = new[] { obj.GetType() };

    // Get a MethodInfo instance that represents the correct overload
    var method = typeof(CrmToRealTypeConverter)
                 .GetMethod("Convert", bFlags, null, parameterTypes, null);

    if (method == null)
        throw new NotSupportedException("Cannot convert " + obj.GetType());

    // Invoke the method with the forwarded argument
    return method.Invoke(this, new object[] { obj });
}  

我总是使用反射方法得到空方法。编辑:不用管了。 - the_drow
它确实是程序员的错误,而不是程序的错误。 - the_drow

2
你应该遵循的模型是.Net Convert类中的模型,没有理由让构造函数成为通用的,这对于实现没有任何帮助。将转换例程更改为静态方法,并将类本身更改为静态:
static class CrmToRealTypeConverter : IConverter
{
    #region IConverter Members

    public static DateTime? Convert(CrmDateTime obj)
    {
        return obj.IsNull == false ? (DateTime?)obj.UserTime : null;
    }

    public static int? Convert(CrmNumber obj)
    {
        return obj.IsNull == false ? (int?)obj.Value : null;
    }

    public static decimal? Convert(CrmDecimal obj)
    {
        return obj.IsNull == false ? (decimal?)obj.Value : null;
    }

    public static double? Convert(CrmDouble obj)
    {
        return obj.IsNull == false ? (double?)obj.Value : null;
    }

    public static float? Convert(CrmFloat obj)
    {
        return obj.IsNull == false ? (float?)obj.Value : null;
    }

    public static decimal? Convert(CrmMoney obj)
    {
        return obj.IsNull == false ? (decimal?)obj.Value : null;
    }

    public static bool? Convert(CrmBoolean obj)
    {
        return obj.IsNull == false ? (bool?)obj.Value : null;
    }
}

那么当您调用其中一个转换方法时,编译器将选择正确的重载进行调用:

CrmDateTime crmDate;
CrmToRealTypeConverter.Convert(crmDate);  // Will call the static DateTime? Convert(CrmDateTime obj) overload    
// or 
CrmNumber crmNum;
CrmToRealTypeConverter.Convert(crmNum);  // Will call the static int? Convert(CrmNumber obj) overload
// and so on...

编辑: 如果您执行以下操作:

CrmFloat num;
// ...
Object obj = num;
CrmToRealTypeConverter.Convert(obj);

由于编译器不知道匹配重载的类型,因此它无法工作。您需要进行强制转换,然后它才能正常工作:

CrmToRealTypeConverter.Convert((CrmFloat)obj);

如果我传递一个对象,会失败吗? - the_drow

1
这是因为编译器在运行时不知道泛型类型 T,并且在编译时将调用绑定到 T = System.Object,而唯一适合使用 System.Object 的函数就是该函数本身。然而,在 .NET 4 中,您可以使用 dynamic 关键字使运行时根据运行时的 T 动态选择正确的重载,这正是您想要发生的事情。
简单示例:
class Main {
    static void somefunction(System.String str)
    {
        System.Console.WriteLine("In String overload");
    }
    static void somefunction(System.Object obj)
    {
        System.Console.WriteLine("In Object overload");
    }
    static void somegenericfunction<T>(T object)
    {
        somefunction(object);
    }
    static void dynamicfunction<T>(dynamic T object)
    {
        somefunction(object);
    }
    static void main(System.String[] args)
    {
        somegenericfunction("A string"); // Calls Object overload, even though it's a String.
        dynamicfunction("A string"); // Calls String overload
    }
}

请注意,我手头实际上没有编译器,这段代码可能无法直接编译通过,但足够接近。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接