c#中与Java' Class类型

本文关键字:类型 Class 中与 Java | 更新日期: 2023-09-27 17:50:31

在Java中,class有泛型形参x很方便,但是c#的Type类没有这个。

那么在c#中,如何完成下面的Java代码呢?:

public <X> X methodThatReturns(Class<X> clazz) { ... }

在c#中似乎没有办法将返回值和传递的Type连接起来。


有几个答案表明方法参数是不必要的,因为方法可以简单地定义为methodThatReturns<X>()

但是如果确实有一些未知的类型变量t,基本上没有办法调用这样一个泛型方法,这样它将返回一个类型为t的对象?

在Java中,您可以自由地传递Class<X>变量而不会丢失类型信息,但似乎在c#中,如果您传递等效的Type变量,您可能会遇到限制,因为当您需要调用泛型方法时无法使用它们。

c#中与Java' Class<X>类型

public X methodThatReturns<X>(Class<X> clazz) { ... }

还请记住,在c#中没有类型擦除,所以您可以做像typeof(T)这样的事情而不用担心,如果Class意味着是java的"类"对象而不是"某些类"占位符:

public X methodThatReturns<X>(X value)
{
    Type x = typeof(X); // that's fine
    if (value is SomeType) { } // that's fine too    
    return (X)someObject; // I think you get the point
}
编辑:

同样,由于泛型类型信息在编译后不会丢失,因此不需要显式地传递类型:

public X methodThatReturns<X>()
{
    Type xType = typeof(X); // Type is to C#/.Net what Class<X> is to Java.
}

在c#中是

public X methodThatReturns<X>() { ... }

,你可以得到类型的X使用typeof(X)而不是使用参数clazz

我不是100%的Java泛型…您是否试图声明一个返回与传递的类型相同的方法?

public T MethodThatReturns<T>(T input) { ... }

此方法将是任何类型的泛型,并返回传递给它的相同类型。你可以使用:

AnyTypeAtAll foo = new AnyTypeAtAll();
AnyTypeAtAll bar = MethodThatReturns(foo);

请注意,在调用MethodThatReturns时没有<AnyTypeAtAll>,编译器可以根据您传递的参数计算出它。但是,请注意,这是编译器在做,而不是运行时,所以它只会使用变量foo的类型,而不是foo指向的对象的类型。


另一方面,如果这是一个不带"真实"参数的方法的Java语法,那么只需:

public T MethodThatReturns<T>()
{
    Type clazz = typeof(T);
    ....
}

在这个方法中,您可以像对待任何其他类一样对待T,并且它将专门引用调用该方法时使用的类型,而不是object或类似的类型。您可能希望将where T : class设置为允许在该方法中对null进行比较(这确实阻止您使用int作为泛型类型),或者将where T : new()设置为限制泛型类型具有默认构造函数,因此您可以执行T foo = new T();

我熟悉Java和。net泛型,我在Java项目中经常使用Class<X>结构。我最喜欢的一个用途是处理从Hibernate等外部代码返回的Java中的非泛型集合(我认为Hibernate的最新版本可能支持泛型,尽管我不确定)。我们正在使用一个相当旧的版本,因为它是一个长期运行的项目,有太多的代码,如果我们改变它,就必须更新。)函数是这样工作的:

public <X> List<X> TypedList(List<?> lst, Class<X> cls) {
    return (List<X>) lst;
}

这非常简单,重要的是我不需要创建一个虚拟对象来传递给函数,我只需要将其命名为:

List<MyObject> myLst = TypedList(lst, MyObject.class);

另一个需要注意的重要事项是根本没有使用cls参数。这是给出x的具体类型的唯一方法。

在c#中做同样的事情的方式有点不同。您可能会认为相同的函数看起来像这样:

public List<X> TypedList<X>(IList lst)
{
    return (List<X>) lst;
}

但是你完全错了。

问题是Java使用了一个叫做Type Erasure的技巧,这基本上意味着当代码实际编译时,所有泛型参数都从代码中删除。因此,在Java虚拟机的世界中,没有List<X>或任何其他泛型,它们都是简单的List和其他非泛型类型。这意味着泛型只是在那里帮助你编写代码,而不是在运行时强制执行。 然而,在c#中没有类型擦除,泛型在运行时强制执行。这意味着当与上述函数一起使用时,以下代码将生成运行时异常:
List<object> lstObj = TypedList<object>(new List<string>());

原因是List<string>List<object>被认为是两个完全不同的类。你甚至不能做List<object> lstObj = (List<object>) new List<string>();而不得到编译器错误。这意味着函数不能返回传递给它的相同对象。必须创建一个正确类型的新对象并返回该对象。最基本的形式如下所示:

public List<X> TypedList<X>(IList lst)
{
    List<X> lstOut = new List<X>();
    for (int i = 0; i < lst.Count; i++)
    {
        if (lst[i] is X) lstOut.Add((X) lst[i]);
    }
    return lstOut;
}

这是相当无聊的样板代码,但它可以工作。对于更短、更简洁的东西,LINQ在这里拯救了一天。查看以下内容:

public List<X> TypedList<X>(IList lst)
{
    return lst.OfType<X>().ToList();
}

OfType<X>()获取list中所有类型为X(或后代)的项,并跳过任何非X类型的项。然后,ToList()构建一个新的List<X>,包含从OfType<X>()传递给它的所有成员。这种方法的另一个优点是,我们实际上可以将lst参数从IList更改为IEnumberable,这是由所有集合类型实现的。

另一件需要指出的事情是,它不做任何类型转换,只做类型检查。因此,这意味着如果您想将List<long>转换为List<int>List<string>,则需要做其他事情。对于LINQ,这仍然非常简单:

List<long> lstLng = new List<long>();
lstLng.Add(1);
List<int> lstInt = lstLng.Cast<int>().ToList();
List<string> lstStr = lstLng.Select(lng => lng.ToString()).ToList();

[注意:如果你不熟悉lng => lng.ToString()部分,它被称为Lambda表达式]

Cast<int>(),与OfType<int>()不同,实际上尝试将每个项转换为int,跳过任何转换失败的项。Select()只是让你从现有的集合中构建一个全新的集合,使用几乎任何你想要的代码。

我希望我的真实例子可以帮助人们更好地理解。net和Java在使用泛型函数时的区别。