简体   繁体   中英

Generic type inheritance

public class BaseGenericType<T>
{
}

public class SubGenericType<T>: BaseGenericType<List<T>>
{
}

I have two generic types above, which one inherits from another but is still generic. The strange thing I can't figure out is that typeof(SubGenericType<>).IsSubclassOf(typeof(BaseGenericType<>)) returns false. And typeof(SubGenericType<>).IsSubclassOf(typeof(BaseGenericType<List<>>)) still returns false. I've tried GetGenericTypeDefinition() and MakeGenericType() and GetGenericArguments() to check the inheritance, still not working. But typeof(SubGenericType<int>).IsSubclassOf(typeof(BaseGenericType<List<int>>)) returns true.

What I want is to get all classes by reflection then grab the specific class which inherits from a generic type passed in.

eg

(1) List<int> -->

(2)get generic type definition ==> List<T> -->

(3)make generic ==> BaseGenericType<List<T>> -->

(4)find subclass ==> SubGenericType<T>

(5)make generic ==> SubGenericType<int>

In step (4) I find nothing although I actually have that SubGenericType<T> . Why is that?

Once I wrote this method to check generic type inheritance:

    static bool IsSubclassOfOpenGeneric(Type generic, Type toCheck)
    {
        while (toCheck != null && toCheck != typeof(object))
        {
            var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
            if (generic == cur)
            {
                return true;
            }
            toCheck = toCheck.BaseType;
        }
        return false;
    }

This returns true:

IsSubclassOfOpenGeneric(typeof(BaseGenericType<>), typeof(SubGenericType<int>))

It doesn't check interfaces though.

By the way, usually if you have relations like this, and you write all the classes yourself, consider using interfaces . It is much easier to handle. You can for instance have a IGenericType interface without generic argument. Sometome you just do not care about the generic type and just want to access members which do not depend on the generic type. Sometimes you want to simply check if it is one of those. And you can use type variance.

Finally, I figured it out. This is the very solution. To explain in details, I have to introduce some non-abstract coding:

It's about a value converter. My purpose is simple, to let users add their own value converters . In the conversion step, I will check built-in types' converters first(as IConvertible ), if not found, I'll first search the current executing assembly for all custom converter classes that inherit a specific abstract class provided by me. And an interface is implemented by that abstract class to make constraint for later reflection. Then I filter those reflected classes for the one that matches.

Here is the base class and interface(all nested):

    private interface ICustomConverter
    {
        Type SourceType { get; }

        object CallConvert(string input);
    }

    public abstract class CustomConverter<T> : ICustomConverter
    {
        public abstract T Convert(string input);

        public Type SourceType
        {
            get { return typeof (T); }
        }

        object ICustomConverter.CallConvert(string input)
        {
            return Convert(input);
        }
    }

I've made the interface private in the parent class and implemented explicitly. So that the method CallConvert() won't be called outside.

The generic parameter T is the Type to convert the string value to. eg

    public class Int32Converter:CustomConverter<int>
    {
    }

This is easy to handle since the conversion target type isn't generic. all I need to do is to get all types that implement ICustomConverter , and make a generic type from CustomConverter<T> with the given int , thus CustomConverter<int> . Then I filter those classes for the one that derives from CustomConverter<int> , and here I found Int32Converter .

Later I came across this situation:

    public class ListConverter<T>:CustomConverter<List<T>>
    {
    }

and

    public class DictConverter<T,U>:CustomConverter<Dictionary<T,U>>
    {
    }

I used the same process to deal with them. But after I made a generic type CustomConverter<List<T>> , I found that ListConverter<T> does not derive from CustomConverter<List<T>> and CustomConverter<List<T>> is not assignable from ListConverter<T> (which I checked with IsAssignableFrom() and IsSubclassOf() ).

I guess the reason is that generic type stands for more than one type before the generic parameters are assigned. This sounds weird but it is true. The compiler doesn't know that the T in CustomConverter<List<T>> and ListConverter<T> stand for the same TYPE In fact, I can write it like CustomConverter<List<T>> and ListConverter<U> , and then you tell me the inheritance relationship between them.

And base type checking won't work here since ListConverter<T> and DictConverter<T,U> share the same root class. This means if I look for ListConverter<T> , I'll get DictConverter<T,U> too with the base class checking method(hierarchy loop checking). So I still have to make generic type, then check generic arguments and do type comparing.

The point is that I need to look for the specific class whose generic parameters are used as the generic arguments in its parent class's generic parameter. Sort of twisted but now it is clear.

Here is the final Convertion solution:

    public static object ToObject(Type type, string value)
        {
            if (type == null)
                throw new ArgumentNullException("type");
            if (!typeof (IConvertible).IsAssignableFrom(type))
            {
                if (type.IsGenericType)
                {
                    Type converterType = typeof (CustomConverter<>).MakeGenericType(type);
                    Type genericConverter =
                        typeof (ICustomConverter).Assembly.Types(Flags.Public)
                            .SingleOrDefault(
                                t =>
                                    typeof (ICustomConverter).IsAssignableFrom(t) && t.IsGenericType &&
                                    t.GetGenericArguments().Length == type.GetGenericArguments().Length && !t.IsAbstract &&
                                    t.MakeGenericType(type.GetGenericArguments()).IsSubclassOf(converterType));
                    if (genericConverter != null)
                    {
                        Type customConverter = genericConverter.MakeGenericType(type.GetGenericArguments());
                        object instance = customConverter.CreateInstance();
                        if (instance is ICustomConverter)
                            return ((ICustomConverter) instance).CallConvert(value);
                    }
                }
                else
                {
                    Type converterType = typeof (CustomConverter<>).MakeGenericType(type);
                    Type customConverter =
                        typeof (ICustomConverter).Assembly.Types(Flags.Public)
                            .SingleOrDefault(t => t.IsSubclassOf(converterType));
                    if (customConverter != null)
                    {
                        object instance = customConverter.CreateInstance();
                        if (instance is ICustomConverter)
                            return ((ICustomConverter) instance).CallConvert(value);
                    }
                }
    
    
                throw new ArgumentException("type is not IConvertible and no custom converters found", type.Name());
            }
            TypeConverter converter = TypeDescriptor.GetConverter(type);
            return converter.ConvertFromString(value);
        }

I also checked GetGenericArguments().Length in case List<T> messes with Dictionary<TKey,TValue> .

Note: some custom extension methods are used.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM