Определите, может ли отраженный тип быть приведен к другому отраженному типу - PullRequest
3 голосов
/ 15 ноября 2008

В .net (C #), если у вас есть два типа, обнаруженных с помощью отражения, можно ли определить, можно ли привести один тип к другому? (неявный и / или явный).

Я пытаюсь создать библиотеку, которая позволяет пользователям указывать, что свойство одного типа сопоставляется со свойством другого типа. Все хорошо, если два свойства имеют совпадающие типы, но я хотел бы иметь возможность позволить им отображать свойства, где доступно неявное / явное приведение. Так что, если у них есть

class from  
{
  public int IntProp{get;set;}
}

class to
{
  public long LongProp{get;set;}
  public DateTime DateTimeProp{get;set;}
}

они могли бы сказать, что from.IntProp будет назначен на .LongProp (поскольку существует приведение к простоте). Но если бы они сказали, что он сопоставлен с DateTimeProp, я смогу определить, что нет доступных приведений, и выдать исключение.

Ответы [ 5 ]

4 голосов
/ 09 января 2011
public static bool HasConversionOperator( Type from, Type to )
        {
            Func<Expression, UnaryExpression> bodyFunction = body => Expression.Convert( body, to );
            ParameterExpression inp = Expression.Parameter( from, "inp" );
            try
            {
                // If this succeeds then we can cast 'from' type to 'to' type using implicit coercion
                Expression.Lambda( bodyFunction( inp ), inp ).Compile();
                return true;
            }
            catch( InvalidOperationException )
            {
                return false;
            }
        }

Это нужно для неявных и явных преобразований (включая числовые типы, классы и т.

2 голосов
/ 15 мая 2014

Вот реализация не очень красивая, но я считаю, что она охватывает все случаи (неявные / явные операторы, блокируемые / распаковываемые блоки, преобразования примитивных типов, стандартные приведения) Обратите внимание, что есть разница между утверждением, что преобразование МОЖЕТ быть успешным против того, что оно МОЖЕТ быть успешным (что почти невозможно узнать наверняка). Для более подробной информации, комплексного юнит-теста и неявной версии, посмотрите мой пост здесь .

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

    // explicit conversion always works if to : from OR if 
    // there's an implicit conversion
    if (from.IsAssignableFrom(to) || from.IsImplicitlyCastableTo(to))
    {
        return true;
    }

    // for nullable types, we can simply strip off the nullability and evaluate the underyling types
    var underlyingFrom = Nullable.GetUnderlyingType(from);
    var underlyingTo = Nullable.GetUnderlyingType(to);
    if (underlyingFrom != null || underlyingTo != null)
    {
        return (underlyingFrom ?? from).IsCastableTo(underlyingTo ?? to);
    }

    if (from.IsValueType)
    {
        try
        {
            ReflectionHelpers.GetMethod(() => AttemptExplicitCast<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, @"^Cannot convert type '.*' to '.*'$")
            );
        }
    }
    else
    {
        // if the from type is null, the dynamic logic above won't be of any help because 
        // either both types are nullable and thus a runtime cast of null => null will 
        // succeed OR we get a runtime failure related to the inability to cast null to 
        // the desired type, which may or may not indicate an actual issue. thus, we do 
        // the work manually
        return from.IsNonValueTypeExplicitlyCastableTo(to);
    }
}

private static bool IsNonValueTypeExplicitlyCastableTo(this Type from, Type to)
{
    if ((to.IsInterface && !from.IsSealed)
        || (from.IsInterface && !to.IsSealed))
    {
        // any non-sealed type can be cast to any interface since the runtime type MIGHT implement
        // that interface. The reverse is also true; we can cast to any non-sealed type from any interface
        // since the runtime type that implements the interface might be a derived type of to.
        return true;
    }

    // arrays are complex because of array covariance 
    // (see http://msmvps.com/blogs/jon_skeet/archive/2013/06/22/array-covariance-not-just-ugly-but-slow-too.aspx).
    // Thus, we have to allow for things like var x = (IEnumerable<string>)new object[0];
    // and var x = (object[])default(IEnumerable<string>);
    var arrayType = from.IsArray && !from.GetElementType().IsValueType ? from
        : to.IsArray && !to.GetElementType().IsValueType ? to
        : null;
    if (arrayType != null)
    {
        var genericInterfaceType = from.IsInterface && from.IsGenericType ? from
            : to.IsInterface && to.IsGenericType ? to
            : null;
        if (genericInterfaceType != null)
        {
            return arrayType.GetInterfaces()
                .Any(i => i.IsGenericType
                    && i.GetGenericTypeDefinition() == genericInterfaceType.GetGenericTypeDefinition()
                    && i.GetGenericArguments().Zip(to.GetGenericArguments(), (ia, ta) => ta.IsAssignableFrom(ia) || ia.IsAssignableFrom(ta)).All(b => b));
        }
    }

    // look for conversion operators. Even though we already checked for implicit conversions, we have to look
    // for operators of both types because, for example, if a class defines an implicit conversion to int then it can be explicitly
    // cast to uint
    const BindingFlags conversionFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
    var conversionMethods = from.GetMethods(conversionFlags)
        .Concat(to.GetMethods(conversionFlags))
        .Where(m => (m.Name == "op_Explicit" || m.Name == "op_Implicit")
            && m.Attributes.HasFlag(MethodAttributes.SpecialName)
            && m.GetParameters().Length == 1 
            && (
                // the from argument of the conversion function can be an indirect match to from in
                // either direction. For example, if we have A : B and Foo defines a conversion from B => Foo,
                // then C# allows A to be cast to Foo
                m.GetParameters()[0].ParameterType.IsAssignableFrom(from)
                || from.IsAssignableFrom(m.GetParameters()[0].ParameterType)
            )
        );

    if (to.IsPrimitive && typeof(IConvertible).IsAssignableFrom(to))
    {
        // as mentioned above, primitive convertible types (i. e. not IntPtr) get special 
        // treatment in the sense that if you can convert from Foo => int, you can convert
        // from Foo => double as well
        return conversionMethods.Any(m => m.ReturnType.IsCastableTo(to));
    }

    return conversionMethods.Any(m => m.ReturnType == to);
}

private static void AttemptExplicitCast<TFrom, TTo>()
{
    // based on the IL generated from
    // var x = (TTo)(dynamic)default(TFrom);

    var binder = Microsoft.CSharp.RuntimeBinder.Binder.Convert(CSharpBinderFlags.ConvertExplicit, typeof(TTo), typeof(TypeHelpers));
    var callSite = CallSite<Func<CallSite, TFrom, TTo>>.Create(binder);
    callSite.Target(callSite, default(TFrom));
}
1 голос
/ 27 ноября 2009

Чтобы прямо ответить на ваш вопрос ...

Если у вас есть два типа, обнаруженных посредством отражения, можно ли определить, можно ли один из них привести к другому? (неявный и / или явный)

... вы можете использовать что-то похожее на это:

to.GetType().IsAssignableFrom(from.GetType());

Метод Type.IsAssignableFrom () может использоваться именно для ваших целей. Это также было бы значительно менее многословно (даже если только немного более производительно), чем использование TypeConverters.

1 голос
/ 15 ноября 2008

Было бы лучше заглянуть в TypeConverter's.

0 голосов
/ 15 ноября 2008

Итак, возможно, вы имеете в виду типирование утки или структурную типизацию? Существует несколько реализаций, которые будут динамически генерировать необходимые прокси.

Например:

http://www.deftflux.net/blog/page/Duck-Typing-Project.aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...