Using IsAssignableFrom with C# generics

C#语言提供了很多高级功能,其中就包括泛型(generic)的引入,泛型避免了类型强制转换的麻烦,提高了代码执行效率,但也带来了一些问题,比如增加了代码量,泛型使用太多引起混乱,此外就是今天要说的问题:Type#IsAssignableFrom方法不正确。

问题

比如:typeof(object).IsAssignableFrom(typeof(string));返回true,而typeof(A<>).IsAssignableFrom(typeof(A<string>))返回false。

这个问题导致判断类型这样的基本操作在C#中“没法”实现,默认我们可以用”is”关键词判断对象是否是某种类型,如:

string a = “ycoder”;

Console.WriteLine(a is object);

A<string> aa = new A<string>();

//Console.WriteLine(aa is A<>);//我们无法判断aa是A<>类型,只能判断aa是否是A<string>或者A<int> … 类型

typeof(A<>).IsAssignableFrom(aa.GetType());//return false我们也没法通过aa.GetType()去判断,得到的是false

而如果不是用泛型,得到的是正确结果

typeof(object).IsAssignableFrom(typeof(string));//return true

解决办法

于是寻求解决之道,stackoverflow上也有人遇到相似的问题(Using IsAssignableFrom with generics),但给出的答案经过我测试,有些问题,于是只能自己解决,最终得到下面的两个函数,通过追踪基类(Type#BaseTpe),以及特殊处理泛型类,追踪泛型定义类(Type#GetGenericTypeDefinition())等途径,实现了这一本不该这么费劲的功能:

下面是测试代码:

using System;

/**
 * Sam Sha - yCoder.com
 *
 * */
namespace Test2
{
	class MainClass
	{
		public static void Main (string[] args)
		{
			string a = "ycoder";
			Console.WriteLine(a is object);
			A aa = new A();
			//Console.WriteLine(aa is A<>);//con't write code like this
			typeof(A<>).IsAssignableFrom(aa.GetType());//return false

			Trace(typeof(object).IsAssignableFrom(typeof(string)));//true
			Trace(typeof(A<>).IsAssignableFrom(typeof(A)));//false

			AAA aaa = new AAA();
			Trace("Use IsTypeOf:");
			Trace(IsTypeOf(aaa, typeof(A<>)));
			Trace(IsTypeOf(aaa, typeof(AA)));
			Trace(IsTypeOf(aaa, typeof(AAA<>)));

			Trace("Use IsAssignableFrom from stackoverflow - not right:");
			Trace(IsAssignableFrom(typeof(A), typeof(A<>))); // error
			Trace(IsAssignableFrom(typeof(AA), typeof(A<>)));
			Trace(IsAssignableFrom(typeof(AAA), typeof(A<>)));

			Trace("Use IsAssignableToGenericType:");
			Trace(IsAssignableToGenericType(typeof(A), typeof(A<>)));
			Trace(IsAssignableToGenericType(typeof(AA), typeof(A<>)));
			Trace(IsAssignableToGenericType(typeof(AAA), typeof(A<>)));
		}

		static void Trace(object log){
				Console.WriteLine(log);
		}

		public static bool IsTypeOf(Object o, Type baseType)
        {
            if (o == null || baseType == null)
            {
                return false;
            }
            bool result = baseType.IsInstanceOfType(o);
            if (result)
            {
                return result;
            }
            return IsAssignableFrom(o.GetType(), baseType);
        }

        public static bool IsAssignableFrom(Type extendType, Type baseType)
        {
            while (!baseType.IsAssignableFrom(extendType))
            {
                if (extendType.Equals(typeof(object)))
                {
                    return false;
                }
                if (extendType.IsGenericType && !extendType.IsGenericTypeDefinition)
                {
                    extendType = extendType.GetGenericTypeDefinition();
                }
                else
                {
                    extendType = extendType.BaseType;
                }
            }
            return true;
        }

		//from stackoverflow - not good enough
		public static bool IsAssignableToGenericType(Type givenType, Type genericType) {
		    var interfaceTypes = givenType.GetInterfaces();

		    foreach (var it in interfaceTypes)
		        if (it.IsGenericType)
		            if (it.GetGenericTypeDefinition() == genericType) return true;

		    Type baseType = givenType.BaseType;
		    if (baseType == null) return false;

		    return baseType.IsGenericType &&
		        baseType.GetGenericTypeDefinition() == genericType ||
		        IsAssignableToGenericType(baseType, genericType);
		}
	}

	class A{}
	class AA : A{}
	class AAA : AA{}
}

运行结果:


5 + 五 =