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

Учитывая тип 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");

person Judah Gabriel Himango    schedule 08.02.2010    source источник


Ответы (4)


Обратите внимание, что 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));
person jason    schedule 08.02.2010
comment
Это скажет нам, что у него есть неявный или явный метод преобразования. Не может ли он быть неявно преобразован между конкретными типами. - person Samuel Neff; 08.02.2010
comment
Все в порядке, Сэм, это сработает для моей проблемы. Я немного удивлен, что нет встроенного способа сделать это. - person Judah Gabriel Himango; 08.02.2010
comment
Почему бы не использовать перечисляемое расширение Any? - person Yuriy Faktorovich; 08.02.2010
comment
@Jason: Вы должны назвать его IsCastableFrom и поменять местами аргументы, чтобы они соответствовали именам аналогичных методов фреймворка. - person Sam Harwell; 08.02.2010
comment
@ 280Z28: Ты прав; оглядываясь назад, IsCastableFrom было бы лучше. - person jason; 08.02.2010
comment
Этот ответ немного устарел. Альтернативой сейчас будет использование dynamic. - person Judah Gabriel Himango; 26.08.2012
comment
неявные/явные операторы могут быть объявлены не только для типа from, но и для типа to. Затем нужно было проверить возвращаемый тип и тип параметра methodinfo оператора на типах to и from. - person shibormot; 30.03.2013

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

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

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

person Hans Passant    schedule 08.02.2010
comment
Хе. Интересный. Я действительно удивлен, что нет встроенного способа сказать, что этот тип может быть преобразован в этот другой тип. - person Judah Gabriel Himango; 08.02.2010
comment
Массив в массив, когда массивы имеют одинаковую длину, а элемент имеет неявное преобразование Вы уверены? Я так не думаю. На самом деле, я не думаю, что есть явное преобразование. Что касается остального, я думаю, что мой метод охватывает их все. Таким образом, я, должно быть, неправильно понимаю, что вы имеете в виду, говоря, что вам нужно написать что-то похожее на компилятор, чтобы охватить их все. - person jason; 08.02.2010
comment
Да, я уверен. Derived[] неявно преобразуется в Base[]. - person Hans Passant; 09.02.2010
comment
Хорошо, да, я согласен, но это немного отличается от вашего первоначального утверждения. Существует неявное преобразование из int в double, но нет неявного преобразования из int[] в double[]. - person jason; 09.02.2010
comment
Да, конечно. Точные правила указаны в спецификации языка, глава 6.1. - person Hans Passant; 09.02.2010
comment
Все вышеперечисленное, кроме op_Implicit и числовых преобразований, покрывается Type.IsAssignableFrom. - person Eamon Nerbonne; 08.12.2010

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

// 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)
{
    if (from == null) { throw new ArgumentNullException(nameof(from)); }
    if (to == null) { throw new ArgumentNullException(nameof(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));
}
person ChaseMedallion    schedule 15.05.2014
comment
Увы, вашего поста на эту тему больше нет по этой ссылке - person Chris F Carroll; 29.09.2018
comment
Пост Conversions.md все еще существует, но статья с описанием ReflectionHelpers.GetMethod, по-видимому, был захвачен киберсквоттером. - person Qwertie; 30.01.2020

Вот метод, который проходит все следующие тесты:

[Test] public void TestImplicitlyCastable()
{
    Assert.That( typeof(byte)    .IsImplicitlyCastableTo(typeof(short)));
    Assert.That( typeof(byte)    .IsImplicitlyCastableTo(typeof(byte?)));
    Assert.That( typeof(byte)    .IsImplicitlyCastableTo(typeof(long?)));
    Assert.That(!typeof(short)   .IsImplicitlyCastableTo(typeof(uint)));
    Assert.That( typeof(long)    .IsImplicitlyCastableTo(typeof(float)));
    Assert.That( typeof(long)    .IsImplicitlyCastableTo(typeof(decimal)));
    Assert.That(!typeof(double)  .IsImplicitlyCastableTo(typeof(decimal)));
    Assert.That(!typeof(decimal) .IsImplicitlyCastableTo(typeof(double)));
    Assert.That( typeof(List<int>).IsImplicitlyCastableTo(typeof(object)));
    Assert.That( typeof(float)   .IsImplicitlyCastableTo(typeof(IComparable<float>)));
    Assert.That( typeof(long?)   .IsImplicitlyCastableTo(typeof(IComparable<long>)));
    Assert.That(!typeof(object)  .IsImplicitlyCastableTo(typeof(string)));
    Assert.That( typeof(string[]).IsImplicitlyCastableTo(typeof(object[])));
    Assert.That( typeof(Foo)     .IsImplicitlyCastableTo(typeof(int)));
    Assert.That(!typeof(Foo)     .IsImplicitlyCastableTo(typeof(uint)));
    Assert.That( typeof(Foo)     .IsImplicitlyCastableTo(typeof(long)));
    Assert.That( typeof(Foo)     .IsImplicitlyCastableTo(typeof(long?)));
}
class Foo
{
    public static implicit operator int(Foo f) => 42;
}

Он основан на трюке с dynamic, вдохновленном ответом ChaseMedallion:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.CSharp.RuntimeBinder;

public static class ReflectionHelpers
{
    [ThreadStatic]
    static readonly Dictionary<KeyValuePair<Type, Type>, bool> ImplicitCastCache;

    /// <summary>Returns true iff casting between values of the specified 
    /// types is possible based on the rules of C#.</summary>
    public static bool IsImplicitlyCastableTo(this Type from, Type to)
    {
        if (from == to)
            return true;

        var key = new KeyValuePair<Type, Type>(from, to);
        ImplicitCastCache ??= new Dictionary<KeyValuePair<Type, Type>, bool>();
        if (ImplicitCastCache.TryGetValue(key, out bool result))
            return result;

        if (to.IsAssignableFrom(from))
            return ImplicitCastCache[key] = true;

        var method = GetMethodInfo(() => IsImplicitlyCastableCore<int, int>())
            .GetGenericMethodDefinition().MakeGenericMethod(from, to);
        return ImplicitCastCache[key] = (bool)method.Invoke(null, Array.Empty<object>());
    }

    static bool IsImplicitlyCastableCore<TFrom,TTo>()
    {
        var testObject = new LinkedListNode<TTo>(default(TTo));
        try {
            ((dynamic)testObject).Value = default(TFrom);
            return true;
        } catch (Exception e) {
            // e.g. "Cannot implicitly convert type 'A' to 'B'. An explicit conversion exists (are you missing a cast?)"
            // The exception may be caused either because no conversion is available,
            // OR because it IS available but the conversion method threw something.
            // Assume RuntimeBinderException means the conversion does not exist.
            return !(e is RuntimeBinderException); 
        }
    }

    /// <summary><c>GetMethodInfo(() => M(args))</c> gets the MethodInfo object corresponding to M.</summary>
    public static MethodInfo GetMethodInfo(Expression<Action> shape) => ((MethodCallExpression)shape.Body).Method;
}
person Qwertie    schedule 30.01.2020