Как определить, можно ли неявно преобразовать тип A в тип B

StackOverflow https://stackoverflow.com/questions/2224266

Вопрос

Учитывая тип a и тип b, как я могу во время выполнения определить, существует ли неявное преобразование из a в b?

Если это не имеет смысла, рассмотрите следующий метод:

public PropertyInfo GetCompatibleProperty<T>(object instance, string propertyName)
{
   var property = instance.GetType().GetProperty(propertyName);

   bool isCompatibleProperty = !property.PropertyType.IsAssignableFrom(typeof(T));
   if (!isCompatibleProperty) throw new Exception("OH NOES!!!");

   return property;   
}

И вот код вызова, с которым я хочу работать:

// Since string.Length is an int property, and ints are convertible
// to double, this should work, but it doesn't. :-(
var property = GetCompatibleProperty<double>("someStringHere", "Length");
Это было полезно?

Решение

Обратите внимание, что IsAssignableFrom НЕ решает вашу проблему.Вы должны использовать Reflection вот так.Обратите внимание на явную необходимость обработки примитивных типов;эти списки соответствуют §6.1.2 (Неявные числовые преобразования) спецификации.

static class TypeExtensions { 
    static Dictionary<Type, List<Type>> dict = new Dictionary<Type, List<Type>>() {
        { typeof(decimal), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } },
        { typeof(double), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
        { typeof(float), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
        { typeof(ulong), new List<Type> { typeof(byte), typeof(ushort), typeof(uint), typeof(char) } },
        { typeof(long), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char) } },
        { typeof(uint), new List<Type> { typeof(byte), typeof(ushort), typeof(char) } },
        { typeof(int), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char) } },
        { typeof(ushort), new List<Type> { typeof(byte), typeof(char) } },
        { typeof(short), new List<Type> { typeof(byte) } }
    };
    public static bool IsCastableTo(this Type from, Type to) { 
        if (to.IsAssignableFrom(from)) { 
            return true; 
        }
        if (dict.ContainsKey(to) && dict[to].Contains(from)) {
            return true;
        }
        bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static) 
                        .Any( 
                            m => m.ReturnType == to &&  
                            (m.Name == "op_Implicit" ||  
                            m.Name == "op_Explicit")
                        ); 
        return castable; 
    } 
} 

Использование:

bool b = typeof(A).IsCastableTo(typeof(B));

Другие советы

Неявные преобразования, которые вам необходимо учитывать:

  • Личность
  • сбайт на короткий, int, длинный, с плавающей запятой, двойной или десятичный
  • от байта к короткому, ushort, int, uint, long, ulong, float, double или decimal
  • от короткого до целого, длинного, с плавающей запятой, двойного или десятичного числа
  • ushort to int, uint, long, ulong, float, double или decimal
  • int в long, float, double или десятичное число
  • uint в long, ulong, float, double или десятичный
  • длинный для плавающей, двойной или десятичной дроби
  • ulong для плавающей, двойной или десятичной дроби
  • char для ushort, int, uint, long, ulong, float, double или decimal
  • плавать до удвоения
  • Преобразование типов, допускающее значение NULL
  • Тип ссылки на объект
  • Производный класс в базовый класс
  • Класс реализованного интерфейса
  • Интерфейс к базовому интерфейсу
  • Массив в массив, когда массивы имеют одинаковое количество измерений, происходит неявное преобразование типа исходного элемента в тип целевого элемента, а тип исходного элемента и тип целевого элемента являются ссылочными типами.
  • Тип массива для System.Array
  • Тип массива для IList<> и его базовых интерфейсов
  • Тип делегата для System.Delegate
  • Конверсия бокса
  • Тип перечисления для System.Enum
  • Пользовательское преобразование (op_implicit)

Я предполагаю, что вы ищете последнее.Вам нужно будет написать что-то вроде компилятора, чтобы охватить их все.Примечательно, что System.Linq.Expressions.Expression не предпринимал таких попыток.

Принятый ответ на этот вопрос касается многих случаев, но не всех.Например, вот лишь несколько допустимых приведений/преобразований, которые обрабатываются неправильно:

// explicit
var a = (byte)2;
var b = (decimal?)2M;

// implicit
double? c = (byte)2;
decimal? d = 4L;

Ниже я опубликовал альтернативную версию этой функции, которая конкретно отвечает на вопрос о неявных приведениях и преобразованиях.Для получения более подробной информации, набора тестов, который я использовал для проверки, и версии EXPLICIT cast, пожалуйста, проверьте мой пост на эту тему.

public static bool IsImplicitlyCastableTo(this Type from, Type to)
{
    // from http://www.codeducky.org/10-utilities-c-developers-should-know-part-one/ 
    Throw.IfNull(from, "from");
    Throw.IfNull(to, "to");

    // not strictly necessary, but speeds things up
    if (to.IsAssignableFrom(from))
    {
        return true;
    }

    try
    {
        // overload of GetMethod() from http://www.codeducky.org/10-utilities-c-developers-should-know-part-two/ 
        // that takes Expression<Action>
        ReflectionHelpers.GetMethod(() => AttemptImplicitCast<object, object>())
            .GetGenericMethodDefinition()
            .MakeGenericMethod(from, to)
            .Invoke(null, new object[0]);
        return true;
    }
    catch (TargetInvocationException ex)
    {
        return = !(
            ex.InnerException is RuntimeBinderException
            // if the code runs in an environment where this message is localized, we could attempt a known failure first and base the regex on it's message
            && Regex.IsMatch(ex.InnerException.Message, @"^The best overloaded method match for 'System.Collections.Generic.List<.*>.Add(.*)' has some invalid arguments$")
        );
    }
}

private static void AttemptImplicitCast<TFrom, TTo>()
{
    // based on the IL produced by:
    // dynamic list = new List<TTo>();
    // list.Add(default(TFrom));
    // We can't use the above code because it will mimic a cast in a generic method
    // which doesn't have the same semantics as a cast in a non-generic method

    var list = new List<TTo>(capacity: 1);
    var binder = Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(
        flags: CSharpBinderFlags.ResultDiscarded, 
        name: "Add", 
        typeArguments: null, 
        context: typeof(TypeHelpers), // the current type
        argumentInfo: new[] 
        { 
            CSharpArgumentInfo.Create(flags: CSharpArgumentInfoFlags.None, name: null), 
            CSharpArgumentInfo.Create(
                flags: CSharpArgumentInfoFlags.UseCompileTimeType, 
                name: null
            ),
        }
    );
    var callSite = CallSite<Action<CallSite, object, TFrom>>.Create(binder);
    callSite.Target.Invoke(callSite, list, default(TFrom));
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top