Почему не происходит перегрузка? - PullRequest
5 голосов
/ 17 октября 2010

У меня есть следующий класс:

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 голосов
/ 17 октября 2010

Полиморфизм не работает с аргументами вызова метода.Подход, который вы можете использовать для проверки типа obj, приведения его к определенному типу и вызова соответствующей перегрузки.

public object Convert(object obj)
{
    if (obj is CrmDateTime)
        return Convert((CrmDateTime)obj);
    if (obj is CrmNumber)
        return Convert((CrmNumber)obj);
    // ...
}
2 голосов
/ 17 октября 2010

Модель, которой вы должны следовать, - это модель в классе .Net Convert, у вас нет причин делать конструктор универсальным, он ничего не приводит к таблице. Измените подпрограммы преобразования на статические методы, а сам класс на static:

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);
2 голосов
/ 17 октября 2010

Позднее связывание происходит не так, как вы думаете; компилятор связывает вызов 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 });
}  
1 голос
/ 17 октября 2010

Это происходит потому, что компилятор не знает универсальный тип 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
    }
}

Обратите внимание, что у меня на самом деле нет моего компилятора, и он может не скомпилироваться буквально, но достаточно близко.

...