20

Just an oddity I happened to discover when I was reflecting over all types to check something else out of curiosity.

Why does the class System.__ComObject of the assembly mscorlib.dll (sometimes?) claim to be public when in fact it seems to be non-public? If I run the following code in a simple C# console application:

var t = Type.GetType("System.__ComObject");
Console.WriteLine(t.IsPublic);   // "True"   ?!
Console.WriteLine(t.IsVisible);  // "False"

the output seems conflicting. A non-nested type (t.IsNested is false) should give the same truth value for IsPublic and IsVisible. When I look at the assembly with IL DASM I see:

.class private auto ansi beforefieldinit System.__ComObject
       extends System.MarshalByRefObject
{
} // end of class System.__ComObject

which, to me, looks very much like a non-public class, something which would correspond to the C# code below:

namespace System
{
    // not public
    internal class __ComObject : MarshalByRefObject
    {
        ...
    }
}

When I compare to another type which has a similar name, System.__Canon, and similar IL modifiers, both IsPublic and IsVisible return false as expected.

Does anyone know why (and when) Type.GetType("System.__ComObject").IsPublic gives true?

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 1
    System.__ComObject and System.__Canon are very different - first is COM object(IsCOMObject==true) - that is why I think it is Public and the second not. – Mikhail Churbanov Aug 07 '13 at 11:45
  • 1
    @Mihail If I use `var t = typeof(Uri).Assembly.GetType("System.Net.Mail.MSAdminBase");` I have another type `t` which also `IsCOMObject`, but `IsNotPublic`. So that seems to be a "counterexample" to that explanation, unless this is something that depends on the assembly (`System.Net.Mail.MSAdminBase` is in another assembly, compared to `System.__ComObject`). – Jeppe Stig Nielsen Aug 07 '13 at 13:24
  • 1
    All I can figure out is, this might be a defect! I came across some similar stuff, may be you guys came across the same too. Follow [link](http://microsoft.public.dotnet.framework.interop.narkive.com/06Y4MWuX/reflection-messing-up-the-type-hierarchy) – Nilesh Aug 09 '13 at 11:15
  • @Nilesh It looks like that link describes the very same problem (or a problem which includes both my question and something more). Unfortunately there is no explanation there. It seems that this situation is unchanged for six years. If it were a bug, that would be strange. But a very interesting comment! – Jeppe Stig Nielsen Aug 09 '13 at 17:41
  • 1
    Congratulations to Noseratio whose answer automatically gave him some reputation after the bounty had expired. However, I still feel my answer is unanswered. Technically `IsPublic` works by calling the abstract `GetAttributeFlagsImpl`, but that doesn't help me understand what's special about `__ComObject`. Whether this behavior is necessary and by design, or it is a bug, I don't know, and it would be interesting to know why `__ComObject` is different than other types here. – Jeppe Stig Nielsen Aug 17 '13 at 07:43

1 Answers1

10

Mono's implementation of __ComObject may give some clues. It is indeed declared as internal, but the comments say "It has no public methods, its functionality is exposed trough System.Runtime.InteropServices.Marshal". I haven't dug into Marshal, but I would assume it is responsible for the implementation of GetType(), so it could customize IsPublic property too.

In my experience, Type.GetType("System.__ComObject").IsPublic is always true. Regarding GetType("System.Net.Mail.MSAdminBase"), I believe it is a COM class exposed via a customized primary interop assembly, where the visibility of a type can be explicitly controlled (although it's just my thinking, no research has been done).

[UPDATE]

I've got the latest Framework source code and found I was wrong about my assumption that customization of IsPublic property for __ComObject Type is done by Marshal. Actually, it is done by unmanaged code. Object.GetType() is defined inside Object.cs like this:

[MethodImplAttribute(MethodImplOptions.InternalCall)]
public extern Type GetType(); 

The unmanaged source code for its implementation in .NET 4.x is not available, but it is available for .NET 2.0. There's an excellent answer about Object.GetType implementation in .NET 2.0. I'd just add, IsPublic is defined by System.Runtime.InteropServices._Type interface which System.Type derives from, and can be overridden by any of System.Type-descendent classes. Apparently, an implementation of such class is returned by Object.GetType, and that's happening inside ObjectNative::GetClass (sscli20\clr\src\vm\comobject.cpp), as shown in the answer:

if (!objRef->IsThunking())
    refType = typeHandle.GetManagedClassObject();
else
    refType = CRemotingServices::GetClass(objRef);

I confirm that the behavior of IsPublic depends on the Framework version. I tried the following PowerShell script in different VMs:

Write-Host ([System.Environment]::Version) ([Type]::GetType("System.__ComObject")).IsPublic

The output is:

2.0.50727.3643 False (WinXP)
4.0.30319.18052 True (Win7)
4.0.30319.19079 True (Win8)

Apparently, it has changed from False to True since .NET 2.0. I agree that True is inconsistent with the declaration of __ComObject (internal class __ComObject ...). I personally see no reason for such change, because the only way to get an instance of __ComObject is through interop.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • I haven't tested this in Mono. I didn't find anything in the source code you linked that seemed relevant, to me. I'm not sure what you mean to say about `GetType` and `IsPublic`. The latter is a non-virtual (though interface implementing) instance property on the abstract `System.Type` class, so I need explanation on how `__ComObject` or `Marshal` "could customize" it (your words). The reason why I say "(sometimes)" in the question, is that I tested this in PowerShell on two different machine, with one machine treating (through PowerShell) `__ComObject` as non-public, apparently. – Jeppe Stig Nielsen Aug 09 '13 at 19:43
  • 1
    I've tried the following with PowerShell: `Write-Host ([System.Environment]::Version) ([Type]::GetType("System.__ComObject")).IsPublic`, the output: `4.0.30319.19079 True`(Win8), `4.0.30319.18052 True` (Win7), `2.0.50727.3643 False` (WinXP). Apparently, the result of `IsPublic` for `__ComObject` depends on the framework version. I'll do some research and provide the explanation on `Marshal` you requested. – noseratio Aug 10 '13 at 00:38
  • Sorry, I'm not sure what you mean by saying that (`IsPublic`) _is a non-virtual (though interface implementing) instance property_ on the abstract `System.Type`. The property is defined in `System.Runtime.InteropServices._Type` interface, so it's virtual by definition and can be overridden by any derived class, same as `IsCOMObject`. `System.Type` doesn't implement `IsPublic`, but some of its derived classes do, e.g. `System.RuntimeType`. I admit I was wrong assuming the customization is done by `Marshal` though, it's actually done with unmanaged code. I've updated my answer with more details. – noseratio Aug 10 '13 at 04:04
  • Regarding PowerShell, I have seen similar behavior. On machines where `__ComObject` seems to be public, you can actually use it directly in PowerShell it seems. For example `[__ComObject]` will give the `Type` object for the class. And `[__ComObject]$x = $null` will declare a variable `$x` of this type, and then `$x = 'Hello'` will fail afterwards because a string will not fit in a `__ComObject` variable. So it really acts "publicly" there. – Jeppe Stig Nielsen Aug 10 '13 at 08:56
  • Regarding `IsPublic`, I was talking about [this member of `System.Type`](http://msdn.microsoft.com/en-us/library/system.type.ispublic.aspx). As you see on that page, in C# terminology this property is non-virtual. However, in Visual C++ the terminology might be different. Looking at the CIL of the associated `get` accessor, the `get_IsPublic` method, wee see that it is declared `virtual final` in that terminology. This is because it implements a member of the interface `_Type`. But virtual-final (or virtual-sealed) still means it can's be overridden in any ordinary way. – Jeppe Stig Nielsen Aug 10 '13 at 09:03
  • About the virtual property, you're right, property as concept is just a syntactic sugar for method, but whether it's virtual or not is a CLR thing, as [discussed here](http://stackoverflow.com/questions/3621410/why-are-c-sharp-interface-methods-not-declared-abstract-or-virtual). `Type` class is not sealed and, regardless of the language syntax differences, I can derive from it and override `IsPublic` (say, with managed C++ or VB.NET). Then I can pass an instance of my class anywhere `Type` is expected, and my implementation of `IsPublic` will be called. I meant just that under customization. – noseratio Aug 10 '13 at 09:34
  • Regarding Powershell `[__ComObject]$x = $null`, this is interesting. Apparently, it means Powershell transparently deals with types through reflection? I didn't know that. Anyway, what you've found about `Type.GetType("System.__ComObject").IsPublic` being `true` really looks like a CLR bug to me :) I'd agree with Nilesh on that. – noseratio Aug 10 '13 at 09:46
  • No, if you derive from `Type` you ***can't*** override `IsPublic`. As I explained it is `sealed`. The behavior of `IsPublic` will be determined by the implementation of [`GetAttributeFlagsImpl` (link)](http://msdn.microsoft.com/en-us/library/system.type.getattributeflagsimpl.aspx) you specify. But this discussion does not seem to be central to the question I asked. Our class `__ComObject` does not derive from `Type`. Why does `__ComObject` behave strangely? – Jeppe Stig Nielsen Aug 10 '13 at 11:51
  • Sorry for going off-topic, I just can't agree that that `Type` is [sealed](http://msdn.microsoft.com/en-us/library/88c54tsw.aspx). It is [abstract](http://msdn.microsoft.com/en-us/library/sf985hc5.aspx) but *not sealed*: `public abstract class Type : MemberInfo, _Type, IReflect {...}`. I can derive from it and override its virtual methods & properties, including `IsPublic`. Indeed, this doesn't help to find the answer. Unfortunately, unmanaged sources for .NET 4.x `Object.GetType` are not available to dig deeper. At this point, I believe it's a design defect, as @Nilesh commented. – noseratio Aug 10 '13 at 12:17