15

我是一个 .NET 人,所以让我首先声明我对一些 Java 概念的理解——如果我错了,请纠正我。

Java 泛型支持有界通配符的概念:

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

...这类似于 .NETwhere限制:

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

Java的Class类描述了一种类型,大致相当于.NETType

到现在为止还挺好。但是我找不到与 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 对应物?

4

4 回答 4

2

使用扩展方法和自定义包装类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 parametersC# 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);
    }
}

这样就可以(明确地)在不相关的接口之间进行转换InterfaceAInterfaceB例如:

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

但这有点违背了练习的目的。

于 2013-07-22T19:57:33.397 回答
1

C# 泛型是声明点变化,类型参数的变化是固定的。

Java 是使用点变体,所以一旦我们有了声明List<E>,我们可以通过 3 种方式使用它

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>、 aWriteOnlyList<in E>和 aList<E>扩展两者。这还不错,有人可能会说这是一个更好的设计。但是如果有更多的类型参数,它可能会变得丑陋。如果一个类的设计者没有预料到它会被多变地使用,那么类的用户就没有办法多变地使用它。

于 2013-01-09T17:12:10.857 回答
0

您可以使用“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(...);

但是在这里我在检查它的类型之前创建实例,这对你来说可能是个问题。

于 2013-01-09T15:19:58.917 回答
0

您可以尝试编写如下扩展方法:

 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>();
于 2013-01-16T03:01:29.397 回答