[C#] 型が同一アセンブリ内 internal アクセスできるか判定する

2022-01-31 (月)

外部アセンブリからアクセスできる型かどうかの public 判定は Type.IsVisibleで可能で、Assembly.GetExportedTypes() で全てのパブリック型を取得できる。
この internal 版の判定を行いたい。public, internal, protected internal で入れ子になったネスト型も含めて、同一アセンブリ内でアクセスできるかどうかを判定する。
ここでは protected による継承でアクセスできるかどうかは判定対象外とする。

環境

  • .NET 6.0.101
  • C# 10.0
  • Visual Studio 2022 Version 17.0.5
  • Windows 10 Pro 64bit 21H1 19043.1466

結果

使用方法

// internal アクセス可能な型を判定 (Type.IsVisible の internal 版)
var isInternalVisible = this.GetType().IsInternalVisible();

// internal アクセス可能な型を全て取得 (Assembly.GetExportedTypes() の internal 版)
var types = Assembly.GetExecutingAssembly()
    .GetTypes()
    .Where(x => x.IsInternalVisible())
    .ToArray();

実装

public static class TypeAccessibilityExtensions
{
    public static bool IsInternalVisible(this Type type)
    {
        return type.GetAccessibility() == Accessibility.Internal;
    }

    private static Accessibility GetAccessibility(this Type type)
    {
        // public
        if (type.IsVisible)
            return Accessibility.Public;

        // public or internal
        var accessibility = GetCore(type);
        if (accessibility == Accessibility.None)
            return Accessibility.None;

        if (!type.IsNested)
            return accessibility;

        // Nested
        var declaringType = type.DeclaringType;
        while (declaringType is not null)
        {
            accessibility = GetCore(declaringType) switch
            {
                Accessibility.Public => accessibility,
                Accessibility.Internal => Accessibility.Internal,
                _ => Accessibility.None,
            };

            if (accessibility == Accessibility.None)
                return Accessibility.None;

            declaringType = declaringType.DeclaringType;
        }

        return accessibility;

        static Accessibility GetCore(Type type)
        {
            if (type.IsPublic || type.IsNestedPublic)
                return Accessibility.Public;

            if (type.IsNotPublic || type.IsNestedAssembly || type.IsNestedFamORAssem)
                return Accessibility.Internal;

            return Accessibility.None;
        }
    }

    private enum Accessibility
    {
        None,
        Public,
        Internal,
    }
}

説明

まず、外部アセンブリからアクセス可能な public 判定は Type.IsVisible で判定する。
ネストされていないトップレベル型も、ネストされている型も考慮されて判定できる。

判定方法アクセスレベル
Type.IsVisiblepublic

次にネストされていないトップレベル型の判定方法であるが、
ネストされていない型は public, internal のみ定義可能であり、
protected internal, private, private protected は定義不可であるため、以下2パターンで判定する。

判定方法アクセスレベル
Type.IsPublicpublic
Type.IsNotPublicinternal

次にネストされている型の場合は、IsNested が付くプロパティで判定する。

判定方法アクセスレベル
Type.IsNestedPublicpublic
Type.IsNestedAssemblyinternal
Type.IsNestedFamORAssemprotected internal

上記で判定できるのはネスト型自身に定義されたアクセス修飾子であるため、ネスト型を包含する型の親階層を全て調べて判断する必要がある。
例えば、ネスト型が internal でもそのネストの親階層の型が private であれば internal と判定しない。

包含したネスト親の型は Type.DeclaringType で取得できる。
また、Type.ReflectedType でも取得できるが、結果は DeclaringType と同じとのこと。

注釈
オブジェクトの場合 Type 、このプロパティの値は常にプロパティの値と同じです DeclaringType 。

(参考) .NET ソース

https://github.com/dotnet/runtime/blob/v6.0.1/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs#L29

public override Type? ReflectedType => DeclaringType;

説明をまとめると、以下のようになる。

private static Accessibility GetAccessibility(this Type type)
{
    // 外部アセンブリからアクセス可能な public 判定 (トップレベル型、ネスト型どちらも考慮済み)
    if (Type.IsVisible)
        return Accessibility.Public;

    // トップレベル型 (public or internal)
    //  or
    // ネスト型 (public or internal or protected internal)
    // の判定
    var accessibility = GetCore(type);
    // 自身が public or internal のアクセス許可していなければ return
    if (accessibility == Accessibility.None)
        return Accessibility.None;

    // トップレベル型であれば、public or internal が確定するので return
    if (!type.IsNested)
        return accessibility;

    // ネスト型では包含する型の親階層をトップレベル型まで調べていく
    var declaringType = type.DeclaringType;
    // トップレベル型まで調べたらループ終了
    while (declaringType is not null)
    {
        // 包含するネスト親の型のアクセス判定
        accessibility = GetCore(declaringType) switch
        {
            // 親が public なら自身のアクセスレベルが適用される (public or internal)
            Accessibility.Public => accessibility,
            // 親が internal なら自身が public でも internal アクセスに制限される (internal)
            Accessibility.Internal => Accessibility.Internal,
            // 親が internal 許可していない
            _ => Accessibility.None,
        };

        // 親が public or internal 許可していなければ終了
        if (accessibility == Accessibility.None)
            return Accessibility.None;

        // さらに包含する親の型がいるか調べる
        declaringType = declaringType.DeclaringType;
    }

    return accessibility;

    static Accessibility GetCore(Type type)
    {
        // public アクセス修飾子で定義された型か判定
        if (type.IsPublic || type.IsNestedPublic)
            return Accessibility.Public;

        // internal, protected internal アクセス修飾子で定義された型か判定
        if (type.IsNotPublic || type.IsNestedAssembly || type.IsNestedFamORAssem)
            return Accessibility.Internal;

        // public, internal のアクセス許可がない型と判定
        return Accessibility.None;
    }
}

検証

検証ソース

AccessibilityClassLibrary1.dll (netstandard2.0)
namespace AccessibilityClassLibrary1
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Enum)]
    public class TypeAccessibilityAttribute : Attribute
    {
        public Accessibility Accessibility { get; }
        public bool IsPublic => Accessibility == Accessibility.Public;
        public bool IsInternal => Accessibility == Accessibility.Internal;
        public TypeAccessibilityAttribute(Accessibility accessibility = Accessibility.None)
        {
            Accessibility = accessibility;
        }
    }

    public enum Accessibility
    {
        None,
        // Private,
        Public,
        // Protected,
        Internal,
        // ProtectedOrInternal,
        // ProtectedAndInternal,
    }

    public static class AccessibilityExtensions
    {
        public static bool IsInternalVisible(this Type type)
        {
            return type.GetAccessibility() == Accessibility.Internal;
        }

        public static Accessibility GetAccessibility(this Type type)
        {
            // public
            if (type.IsVisible)
                return Accessibility.Public;

            // public or internal
            var accessibility = GetCore(type);
            if (accessibility == Accessibility.None)
                return Accessibility.None;

            if (!type.IsNested)
                return accessibility;

            // Nested
            var declaringType = type.DeclaringType;
            while (declaringType is not null)
            {
                accessibility = GetCore(declaringType) switch
                {
                    Accessibility.Public => accessibility,
                    Accessibility.Internal => Accessibility.Internal,
                    _ => Accessibility.None,
                };

                if (accessibility == Accessibility.None)
                    return Accessibility.None;

                declaringType = declaringType.DeclaringType;
            }

            return accessibility;

            static Accessibility GetCore(Type type)
            {
                if (type.IsPublic || type.IsNestedPublic)
                    return Accessibility.Public;

                if (type.IsNotPublic || type.IsNestedAssembly || type.IsNestedFamORAssem)
                    return Accessibility.Internal;

                return Accessibility.None;
            }
        }
    }
}

[TypeAccessibility(Accessibility.Public)]
public enum RootPublicEnum
{
}
[TypeAccessibility(Accessibility.Internal)]
internal enum RootInternalEnum
{
}

[TypeAccessibility(Accessibility.Public)]
public class RootPublicClass
{
    [TypeAccessibility(Accessibility.Public)]
    public enum PublicEnumNested
    {
    }

    [TypeAccessibility(Accessibility.Internal)]
    internal enum InternalEnumNested
    {
    }

    [TypeAccessibility(Accessibility.Public)]
    public class PublicNestedClass
    {
        [TypeAccessibility(Accessibility.Public)] public interface IPublicNested { }
        [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
    }

    protected class ProtectedNestedClass
    {
        public interface IPublicNested { }
        internal interface IInternalNested { }
    }

    [TypeAccessibility(Accessibility.Internal)]
    protected internal class ProtectedInternalNestedClass
    {
        [TypeAccessibility(Accessibility.Internal)] public interface IPublicNested { }
        [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
    }

    [TypeAccessibility(Accessibility.Internal)]
    internal class InternalNestedClass
    {
        [TypeAccessibility(Accessibility.Internal)] public interface IPublicNested { }
        [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
    }

    private protected class PrivateProtectedNestedClass
    {
        public interface IPublicNested { }
        internal interface IInternalNested { }
    }

    private class PrivateNestedClass
    {
        public interface IPublicNested { }
        internal interface IInternalNested { }
    }
}

[TypeAccessibility(Accessibility.Internal)]
internal class RootInternalClass
{
    [TypeAccessibility(Accessibility.Internal)]
    public enum PublicEnumNested
    {
    }

    [TypeAccessibility(Accessibility.Internal)]
    internal enum InternalEnumNested
    {
    }

    [TypeAccessibility(Accessibility.Internal)]
    public class PublicNestedClass
    {
        [TypeAccessibility(Accessibility.Internal)] public interface IPublicNested { }
        [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
    }

    protected class ProtectedNestedClass
    {
        public interface IPublicNested { }
        internal interface IInternalNested { }
    }

    [TypeAccessibility(Accessibility.Internal)]
    protected internal class ProtectedInternalNestedClass
    {
        [TypeAccessibility(Accessibility.Internal)] public interface IPublicNested { }
        [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
    }

    [TypeAccessibility(Accessibility.Internal)]
    internal class InternalNestedClass
    {
        [TypeAccessibility(Accessibility.Internal)] public interface IPublicNested { }
        [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
    }

    private protected class PrivateProtectedNestedClass
    {
        public interface IPublicNested { }
        internal interface IInternalNested { }
    }

    private class PrivateNestedClass
    {
        public interface IPublicNested { }
        internal interface IInternalNested { }
    }
}

namespace My
{
    [TypeAccessibility(Accessibility.Public)]
    public enum PublicEnumNested
    {
    }

    [TypeAccessibility(Accessibility.Internal)]
    internal enum InternalEnumNested
    {
    }

    [TypeAccessibility(Accessibility.Public)]
    public class MyPublicClass
    {
        [TypeAccessibility(Accessibility.Public)]
        public class PublicNestedClass
        {
            [TypeAccessibility(Accessibility.Public)] public interface IPublicNested { }
            [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
        }

        protected class ProtectedNestedClass
        {
            public interface IPublicNested { }
            internal interface IInternalNested { }
        }

        [TypeAccessibility(Accessibility.Internal)]
        protected internal class ProtectedInternalNestedClass
        {
            [TypeAccessibility(Accessibility.Internal)] public interface IPublicNested { }
            [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
        }

        [TypeAccessibility(Accessibility.Internal)]
        internal class InternalNestedClass
        {
            [TypeAccessibility(Accessibility.Internal)] public interface IPublicNested { }
            [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
        }

        private protected class PrivateProtectedNestedClass
        {
            public interface IPublicNested { }
            internal interface IInternalNested { }
        }

        private class PrivateNestedClass
        {
            public interface IPublicNested { }
            internal interface IInternalNested { }
        }

        [TypeAccessibility(Accessibility.Public)] public enum PublicEnumNested { }
        [TypeAccessibility(Accessibility.Internal)] internal enum InternalEnumNested { }
    }

    [TypeAccessibility(Accessibility.Internal)]
    internal class MyInternalClass
    {
        [TypeAccessibility(Accessibility.Internal)]
        public class PublicNestedClass
        {
            [TypeAccessibility(Accessibility.Internal)] public interface IPublicNested { }
            [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
        }

        protected class ProtectedNestedClass
        {
            public interface IPublicNested { }
            internal interface IInternalNested { }
        }

        [TypeAccessibility(Accessibility.Internal)]
        protected internal class ProtectedInternalNestedClass
        {
            [TypeAccessibility(Accessibility.Internal)] public interface IPublicNested { }
            [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
        }

        [TypeAccessibility(Accessibility.Internal)]
        internal class InternalNestedClass
        {
            [TypeAccessibility(Accessibility.Internal)] public interface IPublicNested { }
            [TypeAccessibility(Accessibility.Internal)] internal interface IInternalNested { }
        }

        private protected class PrivateProtectedNestedClass
        {
            public interface IPublicNested { }
            internal interface IInternalNested { }
        }

        private class PrivateNestedClass
        {
            public interface IPublicNested { }
            internal interface IInternalNested { }
        }

        [TypeAccessibility(Accessibility.Internal)] public enum PublicEnumNested { }
        [TypeAccessibility(Accessibility.Internal)] internal enum InternalEnumNested { }
    }
}
LINQPad 7
void Main()
{
    var exportedTypes = typeof(TypeAccessibilityAttribute).Assembly.GetExportedTypes().ToHashSet();

    typeof(TypeAccessibilityAttribute).Assembly
        .GetTypes()
        .Where(x => x.FullName.StartsWith("My") || x.FullName.StartsWith("Root"))
        .Select(t =>
        {
            var info = t.GetTypeInfo();
            return new
            {
                t.FullName,
                Aacessibility = GetAccessibility(t)?.Accessibility.ToString() ?? "",
                GetAacessibility = t.GetAccessibility(),
                IsExported = exportedTypes.Contains(t),
                t.IsVisible,
                t.IsPublic,
                t.IsNotPublic,
                t.IsNested,
                t.IsNestedPublic,
                t.IsNestedFamily,
                t.IsNestedPrivate,
                t.IsNestedAssembly,
                t.IsNestedFamORAssem,
                t.IsNestedFamANDAssem,
                t.Attributes,
            };
        })
        .OrderBy(x => x.FullName)
        .Dump();
}

TypeAccessibilityAttribute? GetAccessibility(Type type)
{
    return type.GetCustomAttribute<TypeAccessibilityAttribute>();
}

検証結果

FullNameAacessibilityGetAacessibilityIsExportedIsVisibleIsPublicIsNotPublicIsNestedIsNestedPublicIsNestedFamilyIsNestedPrivateIsNestedAssemblyIsNestedFamORAssemIsNestedFamANDAssem
My.InternalEnumNestedInternalInternalTrue
My.MyInternalClassInternalInternalTrue
My.MyInternalClass+InternalEnumNestedInternalInternalTrueTrue
My.MyInternalClass+InternalNestedClassInternalInternalTrueTrue
My.MyInternalClass+InternalNestedClass+IInternalNestedInternalInternalTrueTrue
My.MyInternalClass+InternalNestedClass+IPublicNestedInternalInternalTrueTrue
My.MyInternalClass+PrivateNestedClass NoneTrueTrue
My.MyInternalClass+PrivateNestedClass+IInternalNested NoneTrueTrue
My.MyInternalClass+PrivateNestedClass+IPublicNested NoneTrueTrue
My.MyInternalClass+PrivateProtectedNestedClass NoneTrueTrue
My.MyInternalClass+PrivateProtectedNestedClass+IInternalNested NoneTrueTrue
My.MyInternalClass+PrivateProtectedNestedClass+IPublicNested NoneTrueTrue
My.MyInternalClass+ProtectedInternalNestedClassInternalInternalTrueTrue
My.MyInternalClass+ProtectedInternalNestedClass+IInternalNestedInternalInternalTrueTrue
My.MyInternalClass+ProtectedInternalNestedClass+IPublicNestedInternalInternalTrueTrue
My.MyInternalClass+ProtectedNestedClass NoneTrueTrue
My.MyInternalClass+ProtectedNestedClass+IInternalNested NoneTrueTrue
My.MyInternalClass+ProtectedNestedClass+IPublicNested NoneTrueTrue
My.MyInternalClass+PublicEnumNestedInternalInternalTrueTrue
My.MyInternalClass+PublicNestedClassInternalInternalTrueTrue
My.MyInternalClass+PublicNestedClass+IInternalNestedInternalInternalTrueTrue
My.MyInternalClass+PublicNestedClass+IPublicNestedInternalInternalTrueTrue
My.MyPublicClassPublicPublicTrueTrueTrue
My.MyPublicClass+InternalEnumNestedInternalInternalTrueTrue
My.MyPublicClass+InternalNestedClassInternalInternalTrueTrue
My.MyPublicClass+InternalNestedClass+IInternalNestedInternalInternalTrueTrue
My.MyPublicClass+InternalNestedClass+IPublicNestedInternalInternalTrueTrue
My.MyPublicClass+PrivateNestedClass NoneTrueTrue
My.MyPublicClass+PrivateNestedClass+IInternalNested NoneTrueTrue
My.MyPublicClass+PrivateNestedClass+IPublicNested NoneTrueTrue
My.MyPublicClass+PrivateProtectedNestedClass NoneTrueTrue
My.MyPublicClass+PrivateProtectedNestedClass+IInternalNested NoneTrueTrue
My.MyPublicClass+PrivateProtectedNestedClass+IPublicNested NoneTrueTrue
My.MyPublicClass+ProtectedInternalNestedClassInternalInternalTrueTrue
My.MyPublicClass+ProtectedInternalNestedClass+IInternalNestedInternalInternalTrueTrue
My.MyPublicClass+ProtectedInternalNestedClass+IPublicNestedInternalInternalTrueTrue
My.MyPublicClass+ProtectedNestedClass NoneTrueTrue
My.MyPublicClass+ProtectedNestedClass+IInternalNested NoneTrueTrue
My.MyPublicClass+ProtectedNestedClass+IPublicNested NoneTrueTrue
My.MyPublicClass+PublicEnumNestedPublicPublicTrueTrueTrueTrue
My.MyPublicClass+PublicNestedClassPublicPublicTrueTrueTrueTrue
My.MyPublicClass+PublicNestedClass+IInternalNestedInternalInternalTrueTrue
My.MyPublicClass+PublicNestedClass+IPublicNestedPublicPublicTrueTrueTrueTrue
My.PublicEnumNestedPublicPublicTrueTrueTrue
RootInternalClassInternalInternalTrue
RootInternalClass+InternalEnumNestedInternalInternalTrueTrue
RootInternalClass+InternalNestedClassInternalInternalTrueTrue
RootInternalClass+InternalNestedClass+IInternalNestedInternalInternalTrueTrue
RootInternalClass+InternalNestedClass+IPublicNestedInternalInternalTrueTrue
RootInternalClass+PrivateNestedClass NoneTrueTrue
RootInternalClass+PrivateNestedClass+IInternalNested NoneTrueTrue
RootInternalClass+PrivateNestedClass+IPublicNested NoneTrueTrue
RootInternalClass+PrivateProtectedNestedClass NoneTrueTrue
RootInternalClass+PrivateProtectedNestedClass+IInternalNested NoneTrueTrue
RootInternalClass+PrivateProtectedNestedClass+IPublicNested NoneTrueTrue
RootInternalClass+ProtectedInternalNestedClassInternalInternalTrueTrue
RootInternalClass+ProtectedInternalNestedClass+IInternalNestedInternalInternalTrueTrue
RootInternalClass+ProtectedInternalNestedClass+IPublicNestedInternalInternalTrueTrue
RootInternalClass+ProtectedNestedClass NoneTrueTrue
RootInternalClass+ProtectedNestedClass+IInternalNested NoneTrueTrue
RootInternalClass+ProtectedNestedClass+IPublicNested NoneTrueTrue
RootInternalClass+PublicEnumNestedInternalInternalTrueTrue
RootInternalClass+PublicNestedClassInternalInternalTrueTrue
RootInternalClass+PublicNestedClass+IInternalNestedInternalInternalTrueTrue
RootInternalClass+PublicNestedClass+IPublicNestedInternalInternalTrueTrue
RootInternalEnumInternalInternalTrue
RootPublicClassPublicPublicTrueTrueTrue
RootPublicClass+InternalEnumNestedInternalInternalTrueTrue
RootPublicClass+InternalNestedClassInternalInternalTrueTrue
RootPublicClass+InternalNestedClass+IInternalNestedInternalInternalTrueTrue
RootPublicClass+InternalNestedClass+IPublicNestedInternalInternalTrueTrue
RootPublicClass+PrivateNestedClass NoneTrueTrue
RootPublicClass+PrivateNestedClass+IInternalNested NoneTrueTrue
RootPublicClass+PrivateNestedClass+IPublicNested NoneTrueTrue
RootPublicClass+PrivateProtectedNestedClass NoneTrueTrue
RootPublicClass+PrivateProtectedNestedClass+IInternalNested NoneTrueTrue
RootPublicClass+PrivateProtectedNestedClass+IPublicNested NoneTrueTrue
RootPublicClass+ProtectedInternalNestedClassInternalInternalTrueTrue
RootPublicClass+ProtectedInternalNestedClass+IInternalNestedInternalInternalTrueTrue
RootPublicClass+ProtectedInternalNestedClass+IPublicNestedInternalInternalTrueTrue
RootPublicClass+ProtectedNestedClass NoneTrueTrue
RootPublicClass+ProtectedNestedClass+IInternalNested NoneTrueTrue
RootPublicClass+ProtectedNestedClass+IPublicNested NoneTrueTrue
RootPublicClass+PublicEnumNestedPublicPublicTrueTrueTrueTrue
RootPublicClass+PublicNestedClassPublicPublicTrueTrueTrueTrue
RootPublicClass+PublicNestedClass+IInternalNestedInternalInternalTrueTrue
RootPublicClass+PublicNestedClass+IPublicNestedPublicPublicTrueTrueTrueTrue
RootPublicEnumPublicPublicTrueTrueTrue

感謝

2022-01-31 (月)