3

I have a generic method

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

From this method I call another generic method

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

Now in this second generic method, I want to get the real type of T, which is a subtype of AggregateRoot:

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

However, the typeof(T) always returns AggregateRoot.

How can I get the real type (=subtype of AggregateRoot) of T?

stop-cran
  • 4,229
  • 2
  • 30
  • 47
Cpt Slow
  • 370
  • 3
  • 18
  • 1
    GetType should do it – Yair Halberstadt Sep 11 '17 at 08:31
  • 4
    Problem is that you should pass an object of that type to the method itself so that you could then call `GetType` on it. Any other call eg. `typeof(T)` will return compile time type which is irrelevant in your case. – mrogal.ski Sep 11 '17 at 08:32
  • @YairHalberstadt *How* would GetType "do it"? – Fildor Sep 11 '17 at 08:35
  • 1
    Tried it here, it **always uses the subtype if I send in a subtype**. Did you perhaps cast the object to **`AggregateRoot`** before passing it into your `Save()` method? Because then T == AggregateRoot. – Peter B Sep 11 '17 at 08:38
  • 1
    @CptSlow Would it be an option to change the GetAll to `GetAll( Type searchType )` ? Then you could pass the runtime type ... – Fildor Sep 11 '17 at 08:41
  • Sorry. Never realised the object wasnt passed to GetAll... – Yair Halberstadt Sep 11 '17 at 08:43
  • @Peter B: indeed it does, and I tracked the entire callstack before the Save(), the T is never cast explicitly to an AggregateRoot. Still I get AggregateRoot as the type of T in the Save() method, but aggregate.GetType() returns the subtype I need further down. – Cpt Slow Sep 11 '17 at 09:56
  • Please verify your assumption here, the only case where `typeof(T)` would return `AggregateRoot` is if you call `GetAll` in which case, isn't that correct? However, if `_register.FindAll` method returns more specific subtypes, then `.GetType()` will return the actual type of one of the elements. That is also correct. – Lasse V. Karlsen Sep 11 '17 at 10:04
  • Basically, what is your method **actually** supposed to do? Is it supposed to return all objects that *descend* from a specific type? Or *is* a specific type? – Lasse V. Karlsen Sep 11 '17 at 10:05
  • @LasseV.Karlsen: The _register.FindAll() should return all registrations for a certain subtype of AggregateRoot – Cpt Slow Sep 11 '17 at 10:50

4 Answers4

5

typeof(T) is correct here.

I tested your case, and typeof(T) always returns the "true" class, not just the type requirement.

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

The output:

1 - BaseClass
2 - DerivedClass    
3 - BaseClass
4 - DerivedClass

Note that I've simplified the classnames from the values that are actually returned (e.g Sandbox.TestConsole.Program+DerivedClass).

This directly contradicts your claim that you only ever get the base type (AggregateRoot, in your case).


Reflection is an exception to this.

I can think of one exception to this: when your type is only defined at runtime (e.g. generated from a type name (String)).
However, as this StackOverflow answer explains, generics are intended to provide compile time type safety.

It's not impossible to use reflection to instantiate a generic class at runtime. But when you do so, you are inherently preventing the validity of information (e.g. type names) that are decided at compile-time.
MSDN's page on typeof implicitly states that the return value of typeof is the compile-time type.

Combining these two facts, this means that when you use reflection (i.e. deciding the type at runtime), you cannot rely on typeof (as this returns the compile time type).


The linked MSDN page also mentions how to find the runtime type:

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

However, do note that the GetType() method is only available on an instantiated object, not on a generic type parameter. Generic type parameters are not really types, they are much closer to "type placeholders".

You will need either pass the type as a parameter, or an instantiated object (of the appropriate type).


Conclusion:

If you are using types that are known at compile time, then you can simply use typeof(T), as my example has shown.

If you are deciding the type on runtime using reflection, then you cannot rely on the information provided by typeof(T), and will therefore be required to supply the type. Either you supply it as a Type parameter, or you supply it via an instantiated object, whose runtime type can accurately be tested using the GetType() method.

However, if you are already deciding the type on runtime, then you are better off passing the type itself as a parameter. If you're using reflection here, that means that you must at some point have known which type you wanted to use. Therefore, simply pass that known type as a parameter, which you can then use for your subsequent business logic.

I cannot think of a single scenario in which you aren't either (a) using a type that is known at compile time, nor (b) aware (at runtime) of the type you've decided to use.

Community
  • 1
  • 1
Flater
  • 12,908
  • 4
  • 39
  • 62
  • Pardon my ignorance, but in which situation can you get a different type for the Type parameter `T` and the `aggregate` variable in the following method, at run-time : `public async Task Save(T aggregate) where T : AggregateRoot` ? – Cpt Slow Sep 11 '17 at 11:58
  • 1
    @CptSlow: You can't, there is no such situation. If there is a second generic type, then there should be two generic types defined, e.g. `public async Task Save(U aggregate) where T : AggregateRoot`. Note that if the T parameter isn't relevant for this particular method (even though its containing class has a generic type), then you can simply omit the T in the method. E.g. in a class `MyClass`, you can have a method with a separate generic type, e.g. `public void Save(U myObject)`. T and U can be completely different types, if you so choose. – Flater Sep 11 '17 at 12:02
4

As @Flater answer stated, typeof is valid is this case, unless when you instanciated these objects you did not know their types.

So this solution will work for types instanciated at run-time. It will also work if you knew the type at compile time, but it is more complicated and adds complexity.

A solution would be to be able to have an instance of your type, in order to call GetType() on that instance, this would give you the lowest inheriting type of the object.

So, to get an instance, either you change your function and ask for an object to be passed along:

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

Either you create an instance at run-time, and retrieve the type. This require that you mark the generic type as having a parameterless constructor:

public static List<IndexRegistration> GetAll<T>() where T : AggregateRoot, New()
{
    T instance = new T();
    return _register.FindAll(r => r.aggregateType == instance.GetType());
}
Martin Verjans
  • 4,675
  • 1
  • 21
  • 48
  • Your initial claim that it **always** returns `AggregateRoot` is not correct. The code sample in my answer proves that compile time types will accurately be shown by their "true" type, not their "base" type (note that I've mentioned an exception for runtime types, for which your claim is correct, but then you shouldn't say that it **always** returns `AggregateRoot`). – Flater Sep 11 '17 at 11:01
2

There is a similar answer for your question: when-and-where-to-use-gettype-or-typeof.

Generally typeof operator checks for a known at compile-time type. So it will always be AggregateRoot in your case.

Mr.Nimelo
  • 316
  • 4
  • 17
2

To match T itself together with all its inheritors use IsAssignableFrom reflection method, instead of type object comparision:

public static List<IndexRegistration> GetAll<T>() where T : AggregateRoot
{
    return _register.FindAll(r => typeof(T).IsAssignableFrom(r.aggregateType));
}
stop-cran
  • 4,229
  • 2
  • 30
  • 47
  • 3
    Note that `IsAssignableFrom` might give you surprising results in some cases. A variable of type `IEnumerable` may be assigned an object of type `List` for instance, even though `List` implements `IEnumerable`, not `IEnumerable`. – Eric Lippert Sep 11 '17 at 08:32
  • 1
    I doubt this will be a solution, because I suspect all `r`s to comply to this check. T would need to be the runtime type already, not AggregateRoot. – Fildor Sep 11 '17 at 08:32