views:

915

answers:

4
namespace GenericsTest
{
    public class AGenericClass<T>
    {
        public class NestedNonGenericClass
        {
        }
    }
}

In the example above, should NestedNonGenericClass be considered a generic class?

The reflection API says it's a generic class, and even hands me the template parameters of the containing class as the template parameters of the nested class.

Type nestedClass = typeof(AGenericClass<int>.NestedNonGenericClass);
Console.Out.WriteLine("IsGeneric: {0}\tHasGenericArguments: {1}", 
   nestedClass.IsGenericType, nestedClass.GetGenericArguments().Length > 0);

This prints out:

IsGeneric: True HasGenericArguments: True

I don't completely agree with this behaviour. Even if the compiler generates a generic type for NestedNonGenericClass, I'd like to know if it's a generic because it was declared so, or because it's container is generic.

So, my question is:

Firstly, do you think it's okay to consider a nested class generic because it's container was generic? Why / Why not?

Secondly, do you happen to know some other API that can help me identify only the classes that were declared generic?

P.S: I could not find anything related to this in the ECMA specs for generics (or probably I just glossed over it).

--EDIT--

To add a little more context, I'm working on a sort of Code Generator. And I'm using the reflection API to determine whether a type is generic.

I ran into an issue with Dictionary<TKey, TValue>.KeyCollection.

For KeyCollection, the reflection API says that it's generic and hands me over TKey and TValue which were declared in the container. So, the generator ends up generating Dictionary<TKey, TValue>.KeyCollection<Tkey, TValue>

The only way I could solve this was by matching up the nested class' template parameters against the container's and eliminating all those that match. But I was wondering if there's a better approach.

+10  A: 

In short, yes - a type inherits the type arguments from any types that contain it: this is the key to things like List<T>.Enumerator and many other scenarios, etc - it is critical that they share the T from the outer class (not just any T).

The ECMA ref is §25.1:

Any class nested inside a generic class declaration or a generic struct declaration (§25.2) is itself a generic class declaration, since type parameters for the containing type shall be supplied to create a constructed type.

Marc Gravell
Thanks :) That answers my question. But is there a way to distinguish between the classes that were declared with generic templates vs. classes that just inherited generic templates from the container?
CodeMangler
Well, you could see how many type parameters the containing type has (recursing if necessary) and compare that with the number of type parameters in the nested type.
Jon Skeet
Yeah, that's what I'm currently doing. But I was hoping for a better approach. Matching up template parameters against the containing class somehow feels a bit ugly :D
CodeMangler
@Jon, recursing wouldn't be needed since the containing type would already interit of its containers' type parameters.
configurator
+2  A: 

If your not going to use T in NestedNonGenericClass you could just place it outside the class and make it private.. then it would not be Generic....

Petoj
I'm working on a Code Generator (sort of)..I ran into this problem with Dictionary<TKey, TValue>.KeyCollectionBecause the reflection API returns the container's template parameters, my generator ends up generating Dictionary<TKey, TValue>.KeyCollection<TKey, TValue> :(
CodeMangler
+4  A: 

Yes, your nested class absolutely is generic, because T is bound to a type (this is known as a closed generic) within the scope of any instance of the nested class.

using System;
using System.Collections.Generic;

public class AGenericClass<T> {
 public class NestedNonGenericClass {
  public void DoSomething() {
   Console.WriteLine("typeof(T) == " + typeof(T));
  }
 }
}

public class MyClass {
 public static void Main()  {
  var c = new AGenericClass<int>.NestedNonGenericClass();
  var d = new AGenericClass<DateTime>.NestedNonGenericClass();
  c.DoSomething();
  d.DoSomething();
  Console.ReadKey(false); 
 }

}

The same DoSomething() method is producing different output depending on how the generic type was closed - so yes, the inner class is definitely exhibiting generic behaviour.

Dylan Beattie
Yeah, I agree that the inner class displays generic behaviour. But is there a nice way to find out if a type became generic because of the container?
CodeMangler
+2  A: 

The way that I choose to understand it is, when you use a generic type, C# generates everything in that type. So really, if I were to visualise what would be generated if I used AGenericClass and AGenericClass from your example above, you'd end up with two copies of the nested class:

public class AGenericClass<int>
{
    public class NestedNonGenericClass
    {
    }
}

public class AGenericClass<float>
{
    public class NestedNonGenericClass
    {
    }
}

Because of that, I would consider the nested class a generic class, as there are two versions of it, one called AGenericClass<int>.NestedNonGenericClass and another called AGenericClass<float>.NestedNonGenericClass. So really, it is like you did explicitly specify that the nested class was a generic as well. This behaviour can be very useful if you're wanting the nested class to adapt to the generic type.

However, I have found it annoying that I can no longer use nested classes in the same kind of way I do on normal classes. I can't remember the exact example anymore I'm sorry, but I know that once I had to move a class out of a generic so that I could use it as intended from other places in the code. It went against the usual pattern which I had been using for nested classes and so I didn't like it, but it was the only way. So I can understand if you might disagree with the way this is done, but to be honest, I think the way it's done is the clearest way. I mean, it's very clear to you and to the compiler that, if you move the nested class outside and just have it as a regular class, that you don't want the compiler to generate it as a generic. I don't think they could improve on this.

Ray Hidayat