.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>();