【问题标题】:How to get the subtype of a generic class in C#如何在 C# 中获取泛型类的子类型
【发布时间】:2018-02-19 10:18:22
【问题描述】:

我有一个通用方法

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 的子类型)?

【问题讨论】:

  • GetType 应该这样做
  • 问题是您应该将该类型的对象传递给方法本身,以便您可以在其上调用GetType。任何其他电话,例如。 typeof(T) 将返回与您的情况无关的编译时间类型。
  • @YairHalberstadt GetType 会如何“做”?
  • 在这里试过,它如果我发送子类型,它总是使用子类型。您是否在将对象传递给您的 Save() 方法之前将其转换为 AggregateRoot?因为那时 T == AggregateRoot.
  • @CptSlow 是否可以将 GetAll 更改为 GetAll( Type searchType ) ?然后你可以传递运行时类型...

标签: c# generics


【解决方案1】:

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) 生成)。
然而,正如this StackOverflow answer 解释的那样,泛型旨在提供编译时类型安全。

在运行时使用反射来实例化泛型类并非不可能。但是当你这样做时,你本质上会阻止在编译时确定的信息(例如类型名称)的有效性。
MSDN's page on typeof 隐式声明typeof 的返回值是 compile-时间类型。

结合这两个事实,这意味着当您使用反射时(即在 runtime 决定类型),您不能依赖typeof(因为这会返回 compile time强>类型)。


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

要获取表达式的运行时类型,可以使用 .NET Framework 方法 GetType,如下例所示:

int i = 0;
System.Type type = i.GetType();

但是,请注意GetType() 方法仅适用于实例化 对象,不适用于泛型类型参数。泛型类型参数并不是真正的类型,它们更接近于“类型占位符”。

您需要将 type 作为参数传递,或者将 实例化对象(适当的类型)传递。


结论:

如果您使用在编译时已知的类型,那么您可以简单地使用typeof(T),如我的示例所示。

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

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

我想不出一个场景,您既不 (a) 使用在编译时已知的类型,也不 (b) 知道(在运行时)您决定使用的类型。

【讨论】:

  • 请原谅我的无知,但在哪种情况下,您可以在以下方法中为 Type 参数 Taggregate 变量在运行时获得不同的类型:public async Task Save&lt;T&gt;(T aggregate) where T : AggregateRoot
  • @CptSlow:不能,没有这种情况。如果有第二个泛型类型,那么应该定义两个泛型类型,例如public async Task Save&lt;T, U&gt;(U aggregate) where T : AggregateRoot。请注意,如果 T 参数与此特定方法无关(即使其包含的类具有泛型类型),那么您可以简单地省略方法中的 T。例如。在 MyClass&lt;T&gt; 类中,您可以拥有一个具有单独泛型类型的方法,例如public void Save&lt;U&gt;(U myObject)。如果您愿意,T 和 U 可以是完全不同的类型。
【解决方案2】:

正如@Flater 的回答所说,typeof 在这种情况下是有效的,除非您在实例化这些对象时不知道它们的类型。

因此,此解决方案适用于在运行时实例化的类型。如果您在编译时知道类型,它也可以工作,但它更复杂并增加了复杂性。

一种解决方案是能够拥有您类型的实例,以便在该实例上调用GetType(),这将为您提供对象的最低继承类型。

所以,要获得一个实例,要么改变你的函数并要求传递一个对象:

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

你要么create an instance at run-time,然后检索类型。这要求您将泛型类型标记为具有parameterless constructor:

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

【讨论】:

  • 您最初声称它总是返回AggregateRoot是不正确的。我的答案中的代码示例证明编译时类型将通过它们的“真实”类型而不是它们的“基本”类型准确显示(请注意,我提到了运行时类型的异常,您的声明是正确的,但是随后你不应该说它总是返回AggregateRoot)。
【解决方案3】:

您的问题有类似的答案: when-and-where-to-use-gettype-or-typeof.

通常typeof 运算符在编译时检查已知类型。所以在你的情况下它永远是AggregateRoot

【讨论】:

  • 不,这是不正确的。 typeof(T) 其中T 是一个泛型参数将反映正在使用的实际类型,而不是约束。
【解决方案4】:

要匹配T 本身及其所有继承者,请使用IsAssignableFrom 反射方法,而不是类型对象比较:

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

【讨论】:

  • 请注意,IsAssignableFrom 在某些情况下可能会给您带来令人惊讶的结果。例如,IEnumerable&lt;Animal&gt; 类型的变量可以分配给List&lt;Tiger&gt; 类型的对象,即使List&lt;Tiger&gt; 实现IEnumerable&lt;Tiger&gt;,而不是IEnumerable&lt;Animal&gt;
  • 我怀疑这将是一个解决方案,因为我怀疑所有rs 都遵守此检查。 T 需要已经是运行时类型,而不是 AggregateRoot。
猜你喜欢
  • 1970-01-01
  • 2017-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多