如何在C#中获取泛型类的子类型

3

我有一个通用的方法

public async Task Save<T>(T aggregate) where T : AggregateRoot

我从这个方法中调用另一个通用方法。

var indexRegistrations = IndexRegistrar.GetAll<T>();

现在在这个第二种通用方法中,我想获取T的实际类型,它是AggregateRoot的子类型:

public static List<IndexRegistration> GetAll<T>() where T : AggregateRoot
{
    return _register.FindAll(r => r.aggregateType == typeof(T));
}

然而,typeof(T)总是返回AggregateRoot

我该如何获取T的真实类型(即AggregateRoot的子类型)?


1
GetType 应该可以做到。 - Yair Halberstadt
4
问题在于你需要将该类型的对象传递给方法本身,这样才能调用 GetType。任何其他调用,如 typeof(T),都会返回编译时类型,这在你的情况下是无关紧要的。 - mrogal.ski
1
我在这里尝试过了,如果我发送一个子类型,它总是使用子类型。你可能在将对象传递到Save()方法之前将对象强制转换为**AggregateRoot**。因此 T == AggregateRoot。 - Peter B
1
@CptSlow,将GetAll更改为GetAll(Type searchType)是否可行?这样您就可以传递运行时类型... - Fildor
基本上,你的方法实际上应该做什么?它是应该返回所有从特定类型派生的对象吗?还是就是一个特定的类型? - Lasse V. Karlsen
显示剩余5条评论
4个回答

5

typeof(T)在这里是正确的。

我测试了你的案例,typeof(T)总是返回“true”类,而不仅仅是类型要求。

public class BaseClass { }
public class DerivedClass: BaseClass { }

public class GenericClass<T> where T : BaseClass
{
    public string TypeOf = typeof(T).ToString();
}

public class GenericSuperClass<T> where T : BaseClass
{
    public GenericClass<T> Sub = new GenericClass<T>();
}
     
static void Main(string[] args)
{
    Console.WriteLine("1 - " + (new GenericClass<BaseClass>()).TypeOf);
    Console.WriteLine("2 - " + (new GenericClass<DerivedClass>()).TypeOf);

    Console.WriteLine("3 - " + (new GenericSuperClass<BaseClass>()).Sub.TypeOf);
    Console.WriteLine("4 - " + (new GenericSuperClass<DerivedClass>()).Sub.TypeOf);

    Console.ReadLine();
}

输出结果:
1 - BaseClass
2 - DerivedClass    
3 - BaseClass
4 - DerivedClass

请注意,我已经简化了从实际返回值中得到的类名(例如 Sandbox.TestConsole.Program+DerivedClass)。

这直接与您声称只能获得基本类型(在您的情况下为 AggregateRoot)的说法相矛盾。


反射是一个例外。

我可以想到一个例外:当您的类型只在运行时定义时(例如从类型名称(String)生成),就会出现这种情况。
但正如这个StackOverflow答案所解释的那样,泛型旨在提供编译时类型安全性。

使用反射在运行时实例化泛型类并不是不可能。但是当您这样做时,您本质上正在阻止在编译时确定的信息(例如类型名称)的有效性。
MSDN关于typeof的页面隐含地说明,typeof的返回值是编译时类型。

综合这两个事实,这意味着在使用反射(即在运行时决定类型)时,您不能依赖于typeof(因为它返回编译时类型)。


链接的MSDN页面还提到了如何找到运行时类型:

To obtain the run-time type of an expression, you can use the .NET Framework method GetType, as in the following example:

int i = 0;
System.Type type = i.GetType();
然而,请注意GetType()方法仅适用于实例化对象,而不适用于泛型类型参数。泛型类型参数并不是真正的类型,它们更接近于“类型占位符”。
您需要将类型作为参数传递,或者使用一个实例化对象(适当类型的实例化对象)。

结论:

如果您在编译时使用已知类型,则可以简单地使用typeof(T),就像我的示例一样。

如果您在运行时使用反射来决定类型,则不能依赖typeof(T)提供的信息,因此需要提供类型。您可以将其作为Type参数提供,也可以通过一个实例化对象提供,该对象的运行时类型可以使用GetType()方法进行准确测试。

但是,如果您已经在运行时决定了类型,则最好将类型本身作为参数传递。如果您在这里使用反射,那么这意味着您必须在某个时候知道要使用哪种类型。因此,只需将已知类型作为参数传递,然后可以将其用于后续业务逻辑。

我无法想象有任何情况下您既不是(a)在使用编译时已知的类型,也不是(b)在运行时了解您决定使用的类型。


请原谅我的无知,但在以下方法中,在运行时您可以在Type参数Taggregate变量之间获得不同类型的情况是什么:public async Task Save<T>(T aggregate) where T : AggregateRoot - Cpt Slow
1
@CptSlow:不行,这种情况是不存在的。如果有第二个泛型类型,则应该定义两个泛型类型,例如 public async Task Save<T, U>(U aggregate) where T : AggregateRoot。请注意,如果对于此特定方法而言,T 参数并不相关(即使其包含类具有泛型类型),则可以在方法中简单地省略 T。例如,在类 MyClass<T> 中,您可以拥有一个带有单独泛型类型的方法,例如 public void Save<U>(U myObject)。如果您愿意,T 和 U 可以是完全不同的类型。 - Flater

4
如@Flater的答案所述,typeof在这种情况下是有效的,除非您在实例化这些对象时不知道它们的类型。
因此,这种解决方案适用于在运行时实例化的类型。如果您在编译时知道类型,它也可以工作,但会增加复杂性。
一个解决方案是能够拥有您的类型的实例,以便调用GetType(),这将为您提供对象的最低继承类型。
因此,要获取实例,您可以更改函数并要求传递一个对象:
public static List<IndexRegistration> GetAll<T>(T instance) where T : AggregateRoot
{
    return _register.FindAll(r => r.aggregateType == instance.GetType());
}

你可以在运行时创建一个实例,并检索类型。这需要将通用类型标记为具有无参数构造函数

public static List<IndexRegistration> GetAll<T>() where T : AggregateRoot, New()
{
    T instance = new T();
    return _register.FindAll(r => r.aggregateType == instance.GetType());
}

你最初的说法是它总是返回AggregateRoot,这是不正确的。我回答中的代码示例证明了编译时类型将准确显示其“真实”类型,而不是其“基础”类型(请注意,我提到了运行时类型的一个例外,对于这些类型,你的说法是正确的,但你不应该说它总是返回AggregateRoot)。 - Flater

2

为了匹配T及其所有子类,使用反射方法IsAssignableFrom,而不是类型对象比较:

public static List<IndexRegistration> GetAll<T>() where T : AggregateRoot
{
    return _register.FindAll(r => typeof(T).IsAssignableFrom(r.aggregateType));
}

3
请注意,在某些情况下,IsAssignableFrom 可能会给出意外的结果。例如,类型为 IEnumerable<Animal> 的变量可以被赋值为类型为 List<Tiger> 的对象,即使 List<Tiger> 实现了 IEnumerable<Tiger> 而非 IEnumerable<Animal> - Eric Lippert
1
我怀疑这不会是一个解决方案,因为我怀疑所有的r都要遵守这个检查。T需要是运行时类型,而不是AggregateRoot。 - Fildor

2

有一个类似的答案可以回答你的问题:when-and-where-to-use-gettype-or-typeof

typeof 运算符通常用于在编译时检查已知类型。所以在你的情况下它将始终是 AggregateRoot


1
不,那是错误的。typeof(T)中的 T 泛型参数将反映实际使用的类型,而不是约束条件。 - Lasse V. Karlsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接