views:

91

answers:

1

Here's a test that should, in my opinion be passing but is not.

[TestMethod]
public void can_get_open_generic_interface_off_of_implementor()
{
    typeof(OpenGenericWithOpenService<>).GetInterfaces().First()
        .ShouldEqual(typeof(IGenericService<>));
}
public interface IGenericService<T> { }
public class OpenGenericWithOpenService<T> : IGenericService<T> { }
  1. Why does this not pass?
  2. Given Type t = typeof(OpenGenericWithOpenService<>) how do I get typeof(IGenericService<>)?

I'm generally curious, but if you're wondering what I'm doing, I'm writing a Structuremap convention that forwards all interfaces implemented by a class to the implementation (as a singleton).

+4  A: 

OpenGenericWithOpenService<T> doesn't implement just an arbitrary IGenericService<> - it implements IGenericService<T> for the same T as the class.

The best way to show this is to change the class slightly:

public class OpenGenericWithOpenService<T1, T2> : IGenericService<T1> {}

Now it's important that when you ask that for the interfaces it implements, you know that you can convert to IGenericService<T1> but (coincidences aside) not IGenericService<T2> or any other implementation.

In other words, it's not entirely open - it's pinned down to the same type argument that the class has.

I've never been very good with the generics terminology, but I hope you see what I mean. IGenericService<> is a type waiting to be given a type argument; in this case you've got the type argument - it just happens to be another type parameter!

Here's a test which will pass:

[TestMethod]
public void can_get_open_generic_interface_off_of_implementor()
{
    Type[] typeParams = typeof(OpenGenericWithOpenService<>).GetGenericArguments();
    Type constructed = typeof(IGenericService<>).MakeGenericType(typeParams);
    typeof(OpenGenericWithOpenService<>).GetInterfaces().First()            
        .ShouldEqual(constructed);
}

If you change the class to implement (say) IGenericService<int> instead, it will fail.

Jon Skeet
Yeah. Just found more on this here: http://stackoverflow.com/questions/511620/generic-types-not-equalThe test passes if I use `typeof(OpenGenericWithOpenService<>).GetInterfaces().First(). GetGenericTypeDefinition()`
George Mauer
Still don't quite get why `OpenGenericWithOpenService<>` doesn't implement `IGenericService<>`...?
thecoop
Brilliant! I've found that reasoning about the reflection semantics of generics (open or otherwise) is complicated. I think the subject deserves treatment in a book. Hmmm, who do we know that could do that... :)
LBushkin
@thecoop: Suppose we had `class OpenGenericWithOpenService<T1, T2> : IGenericService<T1>, IGenericService<T2>, IGenericService<int>` (with an interface where that was possible). Wouldn't you want to be able to distinguish between the three interfaces it's implementing?
Jon Skeet
Similar gotcha in this blog post: http://blogs.msdn.com/weitao/archive/2008/03/19/formal-type-parameters-of-generic-types.aspx
Hans Passant