26

タイプ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");
4

4 に答える 4

25

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));
于 2010-02-08T19:51:30.437 に答える
6

考慮する必要がある暗黙の変換:

  • 身元
  • sbyteからshort、int、long、float、double、またはdecimal
  • バイトからshort、ushort、int、uint、long、ulong、float、double、またはdecimal
  • shortからint、long、float、double、またはdecimal
  • ushortからint、uint、long、ulong、float、double、またはdecimal
  • intからlong、float、double、またはdecimal
  • uintからlong、ulong、float、double、またはdecimal
  • フロート、ダブル、または10進数に長い
  • フロート、ダブル、または10進数にウロン
  • charからushort、int、uint、long、ulong、float、double、またはdecimal
  • ダブルにフロート
  • null許容型変換
  • オブジェクトへの参照型
  • 派生クラスから基本クラス
  • 実装されたインターフェースへのクラス
  • ベースインターフェースへのインターフェース
  • 配列の次元数が同じである場合、配列から配列へ、ソース要素タイプから宛先要素タイプへの暗黙の変換があり、ソース要素タイプと宛先要素タイプは参照型です
  • System.Arrayへの配列型
  • IList<>とそのベースインターフェイスへの配列型
  • タイプをSystem.Delegateに委任します
  • ボクシングの変換
  • System.Enumへの列挙型
  • ユーザー定義の変換(op_implicit)

私はあなたが後者を探していると思います。それらすべてをカバーするには、コンパイラに似たものを書く必要があります。注目すべきは、System.Linq.Expressions.Expressionがこの偉業を試みなかったことです。

于 2010-02-08T19:58:45.387 に答える
6

この質問に対する受け入れられた答えは、すべてではありませんが、多くのケースを処理します。たとえば、正しく処理されない有効なキャスト/変換は次のとおりです。

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

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

以下に、IMPLICITのキャストと変換の質問に具体的に答えるこの関数の代替バージョンを投稿しました。詳細、検証に使用したテストスイート、およびEXPLICITキャストバージョンについては、この件に関する私の投稿を確認してください。

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));
}
于 2014-05-15T11:54:34.967 に答える
1

次のすべてのテストに合格するメソッドは次のとおりです。

[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;
}
于 2020-01-30T02:30:28.757 に答える