.NET эквивалент для Java типизированного класса <>?

Я парень .NET, поэтому позвольте мне сначала подтвердить свое понимание нескольких понятий Java - исправьте меня, если я ошибаюсь.

Java Generics поддерживает концепцию ограниченных подстановочных знаков:

class GenericClass< ? extends IInterface> { ... }

..., который похож на ограничение .NET where:

class GenericClass<T> where T: IInterface { ... }

Java Class класс описывает тип и примерно эквивалентен .NET Type класс

До сих пор так хорошо. Но я не могу найти достаточно близкую эквивалентность Java, типично типизированной Class<T>, где T - ограниченный подстановочный знак. Это в основном накладывает ограничение на типы, которые представляет Class.

Позвольте мне привести пример на Java.

String custSortclassName = GetClassName(); //only known at runtime, 
                                           // e.g. it can come from a config file
Class<? extends IExternalSort> customClass 
    = Class.forName("MyExternalSort")
        .asSubclass(IExternalSort.class);  //this checks for correctness

IExternalSort impl = customClass.newInstance(); //look ma', no casting!

Ближе всего я могу получить в .NET что-то вроде этого:

String custSortclassName = GetClassName(); //only known at runtime, 
                                           // e.g. it can come from a config file

Assembly assy = GetAssembly();             //unimportant 

Type customClass = assy.GetType(custSortclassName);
if(!customClass.IsSubclassOf(typeof(IExternalSort))){
    throw new InvalidOperationException(...);
}
IExternalSort impl = (IExternalSort)Activator.CreateInstance(customClass);

Версия Java выглядит более чистой для меня. Есть ли способ улучшить .NET-копию?

Ответы

Ответ 1

Используя методы расширения и собственный класс оболочки для System.Type, вы можете приблизиться к синтаксису Java.

ПРИМЕЧАНИЕ. Type.IsSubclassOf не может использоваться для проверки того, реализует ли тип интерфейс - см. связанную документацию на MSDN. Вместо этого можно использовать Type.IsAssignableFrom - см. Код ниже.

using System;

class Type<T>
{
    readonly Type type;

    public Type(Type type)
    {
        // Check for the subtyping relation
        if (!typeof(T).IsAssignableFrom(type))
            throw new ArgumentException("The passed type must be a subtype of " + typeof(T).Name, "type");

        this.type = type;
    }

    public Type UnderlyingType
    {
        get { return this.type; }
    }
}

static class TypeExtensions
{
    public static Type<T> AsSubclass<T>(this System.Type type)
    {
        return new Type<T>(type);
    }
}

// This class can be expanded if needed
static class TypeWrapperExtensions
{
    public static T CreateInstance<T>(this Type<T> type)
    {
        return (T)Activator.CreateInstance(type.UnderlyingType);
    }
}

Дальнейшие улучшения с использованием дисперсии интерфейса

(Должен использоваться только в производственном коде после оценки производительности. Может быть улучшено с помощью (параллельного!) словаря кеша ConcurrentDictionary<System.Type, IType<object>)

Используя Covariant type parameters, функцию, введенную с С# 4.0, и дополнительный тип interface IType<out T>, который реализуется Type<T>, можно было бы сделайте следующее:

// IExternalSortExtended is a fictional interface derived from IExternalSort
IType<IExternalSortExtended> extendedSort = ...
IType<IExternalSort> externalSort = extendedSort; // No casting here, too.

Можно даже сделать:

using System;

interface IType<out T>
{
    Type UnderlyingType { get; }
}

static class TypeExtensions
{
    private class Type<T> : IType<T>
    {
        public Type UnderlyingType
        {
            get { return typeof(T); }
        }
    }

    public static IType<T> AsSubclass<T>(this System.Type type)
    {
        return (IType<T>)Activator.CreateInstance(
           typeof(Type<>).MakeGenericType(type)
        );
    }
}

static class TypeWrapperExtensions
{
    public static T CreateInstance<T>(this IType<T> type)
    {
        return (T)Activator.CreateInstance(type.UnderlyingType);
    }
}

Итак, можно (явно) отличать между несвязанными интерфейсами InterfaceA и InterfaceB как:

var x = typeof(ConcreteAB).AsSubclass<InterfaceA>();
var y = (IType<InterfaceB>)x;

но этот вид побеждает цель упражнения.

Ответ 2

Генерирование С# - это дисперсия объявления-сайта, дисперсия параметра типа фиксирована.

Java - это вариация использования сайта, поэтому, если у нас есть объявление List<E>, мы можем использовать его тремя способами

List<Number>           // invariant, read/write
List<+Number>          // covariant, read only
List<-NUmber>          // contravariant, write only

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

List<Integer> integers = ...;
List<+Number> numbers = integers;  // covariant

К сожалению, Java придумал абсолютно отвратительный синтаксис,

List<? extends Number>    //  i.e. List<+Number>

как только ваш код имеет несколько из них, он становится действительно уродливым. Вы должны научиться преодолевать это.

Теперь, в лагере на сайте объявлений, как нам достичь 3 отклонений в одном классе? Имея больше типов - a ReadOnlyList<out E>, a WriteOnlyList<in E> и a List<E>, расширяющие оба. Это не так уж плохо, и можно сказать, что это лучший дизайн. Но это может стать уродливым, если есть больше параметров типа. И если разработчик класса не ожидал, что он будет использоваться в качестве варианта, пользователи класса не смогут использовать его в качестве варианта.

Ответ 3

Вы можете получить немного более красивую версию, используя оператор "as":

String custSortclassName = GetClassName();
Assembly assy = GetAssembly();
Type customClass = assy.GetType(custSortclassName);

IExternalSort impl = Activator.CreateInstance(customClass) as IExternalSort;
if(impl==null) throw new InvalidOperationException(...);

Но здесь я создаю экземпляр перед проверкой его типа, что может быть проблемой для вас.

Ответ 4

Вы можете попробовать написать метод расширения следующим образом:

 static class TypeExtension
    {
        public static I NewInstanceOf<I>(this Type t) 
            where  I: class 
        {
            I instance = Activator.CreateInstance(t) as I;
            if (instance == null)
                throw new InvalidOperationException();
            return instance;
        }
    }

который затем можно использовать следующим образом:

String custSortclassName = GetClassName(); //only known at runtime, 
                                           // e.g. it can come from a config file

Assembly assy = GetAssembly();
Type customClass = assy.GetType(custSortclassName);            

IExternalSort impl = customClass.NewInstanceOf<IExternalSort>();