views:

690

answers:

3

I have the following situation:

// A public interface of some kind   
public interface IMyInterface {   
    int Something { get; set; }   
}   

// An internal class that implements the public interface.   
// Despite the internal/public mismatch, this works.   
internal class MyInternalConcrete : IMyInterface {   
    public int Something { get; set; }   
}   

// A generic class with an interface-restricted type parameter.
// Note that the constraint on T uses the *public* interface.
// The instance is *never* exposed as a public, or even protected member.
public class MyClass<T> where T : IMyInterface, new() {   
    T myInterfaceInstance;   

    public MyClass() {   
        myInterfaceInstance = new T();   
    }   
}   

// Attempting to implement concrete class... Inconsistent Accessibility Error!   
public class MySpecificClass : MyClass<MyInternalConcrete>   
{   
}

When trying to implement MySpecificClass, I get the error "Inconsistent accessibility: base class 'App1.MyClass' is less accessible than class 'App1.MySpecificT'".

Where it gets weird is that MyInternalConcrete, despite being internal, can still implement a public interface. And since it implements the interface, then it should be useable as a type parameter for MyClass - because T is constrained on the public interface and not the internal class.

I would understand it failing if MyClass exposed T, just as it would fail if we weren't using generics:

public class MyClass<T> where T : IMyInterface, new() {      
    T myInterfaceInstance;      

    public MyClass() {      
        myInterfaceInstance = new T();      
    }      

    // This will fail with an internal T - inconsistent accessibility!    
    public T Instance {      
        get { return myInterfaceInstance; }      
    }      
}

And same as above, but without generics:

public class MyNonGenericClass {   
    MyInternalConcrete myInterfaceInstance;   

    public MyNonGenericClass() {   
        myInterfaceInstance = new MyInternalConcrete();   
    }   

    // This will fail - inconsistent accessibility! 
    // but removing it works, since the private instance is never exposed.   
    public MyInternalConcrete Instance {   
        get { return myInterfaceInstance; }   
    }   
}

Is this a limitation of the C# generics or am I simply misunderstanding something fundamental about how generics work?

I also posted this thread on MSDN, but I'm being dismissed as not knowing what I'm talking about. Is my concern even valid?

+6  A: 

According to this article on C# generics

"The visibility of a generic type is the intersection of the generic type with the visibility of the parameter types. If the visibility of all the C, T1, T2 and T3 types is set to public, then the visibility of C is also public; but if the visibility of only one of these types is private, then the visibility of C is private."

So whilst your example could be possible, it doesn't fit with the rules as defined.

For a more definitive source see section 25.5.5 (page 399) of the C# spec.

A constructed type C is accessible when all of its components C, T1, ..., TN are accessible. More precisely, the accessibility domain for a constructed type is the intersection of the accessibility domain of the unbound generic type and the accessibility domains of the type arguments.

Rob Walker
So, a limitation of the generics implementation? Worth reporting on connect?
Ilia Jerebtsov
It doesn't seem logical to allow references to types that your assembly cannot strongly reference.
vanslly
Fantastic. Thanks! That quote about the intersection of types led me to the source of my issue!
kdmurray
+3  A: 

This constraint you are facing makes sense for the following reason.

C# is strongly typed so...

To be able to reference the MySpecificClass outside the scope of the assembly it is defined in you must know its parameter types in order to generate a strong type reference to its instance; but an separate assembly than the internal definition does not know about MyInternalConcrete.

Thus the following wont work if in a separate assembly:

MyClass<MyInternalConcrete> myInstance = new MySpecificClass();

Here the separate assembly doesn't know of MyInternalConcrete, so how can you define a variable as such.

vanslly
This argument is foolproof. Point absolutely understood.
Ilia Jerebtsov
Thanks - my mind has recently been hovering in this space - glad I could add my two cents worth :)
vanslly
I hadn't remembered that you could downcast back to generic definition. It explains perfectly why it needs to know of all the types that make up the definition. This doesn't happen in a regular class definition, so I assumed the same applied for generics. This makes perfect sense now.
Ilia Jerebtsov
+1  A: 

If that were allowed, you could pass a MyClass out of the assembly, and all of a sudden, another assembly has access to something it shouldn't - MyInternalConcrete!

Ray Hidayat