18

In case the title is not completely self-explanatory, here's the code that puzzles me:

public interface IFoo<T>
{
}

public class MyClass : IFoo<MyClass.NestedInMyClass>
{
  private class NestedInMyClass
  {
  }
}

I'm suprised this compiles with no error. It feels like I'm exposing a private type. Shouldn't this be illegal?

Maybe your answers will simply be "There's no rule against that, so why wouldn't it be OK?" Perhaps it's equally surprising that MyClass.NestedInMyClass is even in "scope". If I remove the MyClass. qualification, it will not compile.

(If I change IFoo<> into a generic class, which should then become base class of MyClass, this is illegal, for a base type must be at least as accessible as the type itself.)

I tried this with the C# 4 compiler of Visual Studio 2010.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 2
    I wonder what will happen if you create a function in IFoo, that returns or accepts T. I don't have PC to test it on right now. – Euphoric Jan 07 '13 at 15:43
  • @Euphoric I was just about to ask that. Most likely, this is only allowed, because IFoo is an interface and as such cannot "do" anything with the nested class. In that case, exposing it in a method should be illegal. That would make this feature highly useless, as far as I can see. – Wutz Jan 07 '13 at 15:45
  • @Euphoric Just tried doing exactly that. Added a method to `Foo` that accepts a `T`, implemented a (noop) implementation, and the method is callable on an instance of `MyClass` (I could only pass in `null` to make it compile though, as I couldn't get an instance externally). Note that if the method returns a `T` then you'll get compiler errors for "Inconsistent accessibility" by exposing a `NestedInMyClass` externally. – Servy Jan 07 '13 at 15:47
  • @Euphoric It looks like it's impossible to write the implementation of `IFoo` if `IFoo` contains any member that uses `T`. – Jeppe Stig Nielsen Jan 07 '13 at 15:49
  • @Euphoric Not sure. If you can pass in a `T` then the caller can never get a `T`, they can only pass in `null`. Since no `T` can be returned this way to expose a private class publicly... – Servy Jan 07 '13 at 15:50
  • @Servy Hmm, it doesn't work for me. – Jeppe Stig Nielsen Jan 07 '13 at 15:51
  • @JeppeStigNielsen C# 5.0 compiler for me, which may be the difference. – Servy Jan 07 '13 at 15:52
  • 1
    @JeppeStigNielsen Have you tried explicit interface implementation? – CodesInChaos Jan 07 '13 at 15:52
  • @Servy and CodesInChaos You're right, it works for me to with explicit interface implementation. I tried with a usual `public` instance method, and of course it gave me "inconsistent accessibility" on that. – Jeppe Stig Nielsen Jan 07 '13 at 16:00

2 Answers2

10

No external code can cast the object to this interface, so it's not an accessibility issue.

public classes are even allowed to implement private or internal interfaces - and similarly, no cast can actually occur from external code.

Re: discussion about implementations that depend on T - you'll be allowed it if you use explicit interface implementation - because the methods for the interface are effectively private in that case. E.g.:

public interface IFoo<T>
{
  void DoStuff(T value);
}

public class MyClass : IFoo<MyClass.NestedInMyClass>
{
  void IFoo<MyClass.NestedInMyClass>.DoStuff(MyClass.NestedInMyClass value)
  {
  }
  private class NestedInMyClass
  {
  }
}

works (because the interface implementing method isn't exposed by the class itself).

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • 1
    @Servy Are you sure the method was public and NestedInMyClass was private? – Euphoric Jan 07 '13 at 15:55
  • 1
    @Servy Can you post your code? I get an inconsistent accessibility error when I mark that method as public. – CodesInChaos Jan 07 '13 at 15:57
  • nevermind, you're right. I was playing around and can't replicate it again, must have had something not quite right. – Servy Jan 07 '13 at 15:57
  • Then what if a `public` (non-nested) class `C` implements `IEnumerable` where `Secret` is a type which is not `public`? (For this example, `C` contains no `public` `GetEnumerator()` method, only explicit interface implementations.) Suppose someone can see `C` and has an instance of it, but cannot see `Secret` because of accessibility. Can they `foreach` through their instance? I guess they can because of the non-generic `IEnumerable` interface. What if the `foreach` variable is implicitly typed (the `var` in `foreach (var x in instanceOfC) { ... }`)? – Jeppe Stig Nielsen Jan 07 '13 at 17:07
  • If they implement `IEnumerable` then they *must* use explicit interface implementation. That means you can only access `IEnumerator`s `GetEnumerator` method once you *cast* it to `IEnumerable` - it's not a public method on the class. And you can't do the cast because `Secret` isn't accessible. (All of this "can't cast" stuff, of course, ignores Reflection) – Damien_The_Unbeliever Jan 07 '13 at 17:25
  • I understand that. I'm no longer near a C# compiler, but I **fear** that the `var` in my `foreach` example could be very problematic. This is based on the section "The `foreach` statement" of the C# Language Specification. But I would like to know how it's implemented by Visual C#. – Jeppe Stig Nielsen Jan 07 '13 at 17:47
  • @JeppeStigNielsen - I think I misread your previous comment. Yes, I think you could still access the `IEnumerable` (non generic) interface, but obviously, its results are typed as `object`, so that's what `var` would infer if you used that interface. And, since day 1 in .NET, it's always been possible to be handed an `object` by some piece of code that is of a type that you have no access to. – Damien_The_Unbeliever Jan 07 '13 at 18:24
  • I tried it, and surely `foreach` ignores the generic `IEnumerable` interface if the `Secret` type is not visible because of its access level (for example `private` or `internal`). Not only does `var` translate to `object` in that case, the `foreach` statement also uses the other explicit interface implementation (the one with non-generic return type). It makes sense. But in the [C# specification](http://msdn.microsoft.com/en-us/library/ms228593.aspx) they talk about whether there's an implicit conversion to `IEnumerable` for some `T`. Apparently only the `T` that are accessible. – Jeppe Stig Nielsen Jan 08 '13 at 22:49
  • Also, they changed the `foreach` logic when multiple `IEnumerable<>` interfaces are implemented (and no public `GetEnumerator()` method is there), from C# 4 to C# 5. Interestingly, if **in C# 4** I implement two `IEnumerable<>`, one is of a "visible" type, the other is `IEnumerable`, the compiler will refuse to translate the code, saying _error CS1640: foreach statement cannot operate on variables of type '(type)' because it implements multiple instantiations of 'System.Collections.Generic.IEnumerable'; try casting to a specific interface instantiation_ (to be continued) – Jeppe Stig Nielsen Jan 09 '13 at 10:23
  • Then if I remove the `IEnumerable` so that I only have `IEnumerable` and the non-generic `IEnumerable`, it does not use `IEnumerable` of course. – Jeppe Stig Nielsen Jan 09 '13 at 10:25
1

The fact that a class implements an interface means that code which can create a storage location of an interface type can store a reference to that type in that storage location, and can use interface members on that storage location. It does not give any new ability to code which would not otherwise have the ability to create a storage location of that type.

Having a public class Foo implement an interface of a private or internal type IBar enables code which has access to IBar to cast Foo references to IBar. The fact that Foo is accessible to code which doesn't have access to IBar in no way implies that it isn't also going to be used by code which does have such access. Indeed, it would be quite normal that the assembly or class where Foo is defined would want to use features of Foo which are not available to the outside world; the fact that it implements IBar would merely be one such feature.

supercat
  • 77,689
  • 9
  • 166
  • 211