web-dev-qa-db-ja.com

ネストされたクラスメンバーへのアクセスを囲んでいるクラスに制限する方法は?

ネストされたクラスのメンバーは、それを囲むクラスからはアクセスできるが、他のクラスからはアクセスできないように指定することはできますか?

これが問題の説明です(もちろん、実際のコードはもう少し複雑です...):

public class Journal
{
    public class JournalEntry
    {
        public JournalEntry(object value)
        {
            this.Timestamp = DateTime.Now;
            this.Value = value;
        }

        public DateTime Timestamp { get; private set; }
        public object Value { get; private set; }
    }

    // ...
}

クライアントコードがJournalEntryのインスタンスを作成しないようにしたいのですが、Journalがインスタンスを作成できる必要があります。コンストラクターをパブリックにすると、誰でもインスタンスを作成できますが、プライベートにすると、Journalでインスタンスを作成できなくなります。

既存のエントリをクライアントコードに公開できるようにしたいので、JournalEntryクラスはパブリックでなければなりません。

任意の提案をいただければ幸いです!


更新:皆さんの入力に感謝します。私は最終的に、プライベートIJournalEntryクラスによって実装されたパブリックJournalEntryインターフェースに行きました(私の質問の最後の要件にもかかわらず...)

57
Thomas Levesque

クラスが複雑すぎない場合は、公開されているインターフェースを使用して実際の実装クラスをプライベートにするか、JornalEntryクラスの保護されたコンストラクターを作成し、JornalEntryInstanceから派生したプライベートクラスJornalEntryを、 Journalによって実際にインスタンス化されます。

public class Journal
{
    public class JournalEntry
    {
        protected JournalEntry(object value)
        {
            this.Timestamp = DateTime.Now;
            this.Value = value;
        }

        public DateTime Timestamp { get; private set; }
        public object Value { get; private set; }
    }

    private class JournalEntryInstance: JournalEntry
    { 
        public JournalEntryInstance(object value): base(value)
        { }
    }
    JournalEntry CreateEntry(object value)
    {
        return new JournalEntryInstance(value);
    }
}

実際のクラスが複雑すぎてどちらも実行できない場合、コンストラクターを完全に非表示にしないで済むのであれば、コンストラクターを内部にすると、アセンブリーでのみ表示されます。

それも実行できない場合は、いつでもコンストラクタをプライベートにして、リフレクションを使用してジャーナルクラスから呼び出すことができます。

typeof(object).GetConstructor(new Type[] { }).Invoke(new Object[] { value });

今考えてみると、別の可能性は、内部クラスから設定された包含クラスでプライベートデリゲートを使用することです。

public class Journal
{
    private static Func<object, JournalEntry> EntryFactory;
    public class JournalEntry
    {
        internal static void Initialize()
        {
            Journal.EntryFactory = CreateEntry;
        }
        private static JournalEntry CreateEntry(object value)
        {
            return new JournalEntry(value);
        }
        private JournalEntry(object value)
        {
            this.Timestamp = DateTime.Now;
            this.Value = value;
        }

        public DateTime Timestamp { get; private set; }
        public object Value { get; private set; }
    }

    static Journal()
    {
        JournalEntry.Initialize();
    }

    static JournalEntry CreateEntry(object value)
    {
        return EntryFactory(value);
    }
}

これにより、遅いリフレクションに頼ったり、追加のクラス/インターフェースを導入したりする必要なく、目的の可視性レベルが得られます。

43
Grizzly

実際、クライアントコードの変更やインターフェイスの作成を含まない、この問題に対する完全で単純な解決策があります。

このソリューションは、ほとんどの場合、インターフェイスベースのソリューションよりも実際に高速で、コーディングが簡単です。

public class Journal
{
  private static Func<object, JournalEntry> _newJournalEntry;

  public class JournalEntry
  {
    static JournalEntry()
    {
      _newJournalEntry = value => new JournalEntry(value);
    }
    private JournalEntry(object value)
    {
      ...
77
Ray Burns

JournalEntryprivatenested typeにします。パブリックメンバーは、それを含む型に対してのみ表示されます。

public class Journal
{
    private class JournalEntry
    {
    }
}

JournalEntryオブジェクトを他のクラスで使用できるようにする必要がある場合は、パブリックインターフェイスを介してそれらを公開します。

public interface IJournalEntry
{
}

public class Journal
{
    public IEnumerable<IJournalEntry> Entries
    {
        get { ... }
    }

    private class JournalEntry : IJournalEntry
    {
    }
}
20
Sam Harwell

より単純な方法は、internalコンストラクターを使用するだけですが、-正当な呼び出し元のみが知ることができる参照を提供することにより、呼び出し元が誰であるかを証明します(必要はありません)呼び出し側が非公開リフレクションにアクセスできる場合、私たちはすでに戦いに負けているので、非公開リフレクションに懸念があります-privateコンストラクタに直接アクセスできます);例えば:

class Outer {
    // don't pass this reference outside of Outer
    private static readonly object token = new object();

    public sealed class Inner {
        // .ctor demands proof of who the caller is
        internal Inner(object token) {
            if (token != Outer.token) {
                throw new InvalidOperationException(
                    "Seriously, don't do that! Or I'll tell!");
            }
            // ...
        } 
    }

    // the outer-class is allowed to create instances...
    private static Inner Create() {
        return new Inner(token);
    }
}
12
Marc Gravell

この場合、次のいずれかを行うことができます。

  1. コンストラクタを内部にします-これにより、このアセンブリの外部にあるインスタンスが新しいインスタンスを作成するのを停止します...
  2. JournalEntryクラスをリファクタリングしてパブリックインターフェイスを使用し、実際のJournalEntryクラスをプライベートまたは内部にします。次に、実際の実装が非表示になっている間、インターフェイスをコレクションに公開できます。

上記の有効な修飾子として内部を説明しましたが、要件によっては、プライベートの方が適している場合があります。

編集:申し訳ありませんが、プライベートコンストラクターについて言及しましたが、質問のこの点についてはすでに扱っています。正しくお読みいただけなかったことをお詫び申し上げます。

3
Paul Mason

グリズリーが提案した解決策は、ネストされたクラスをどこか他の場所に作成することを少し難しくしますが、不可能ではありません。TimPohlmannは、誰かがそれを継承して継承クラスのctorを使用できると書いているようです。

ネストされたクラスがコンテナーのプライベートプロパティにアクセスできるという事実を利用しているため、コンテナーは適切に要求し、ネストされたクラスはctorにアクセスできます。

 public class AllowedToEmailFunc
{
    private static Func<long, EmailPermit> CreatePermit;

    public class EmailPermit
    {
        public static void AllowIssuingPermits()
        {
            IssuegPermit = (long userId) =>
            {
                return new EmailPermit(userId);
            };
        }

        public readonly long UserId;

        private EmailPermit(long userId) 
        {
            UserId = userId;
        }
    }

    static AllowedToEmailFunc()
    {
        EmailPermit.AllowIssuingPermits();
    }

    public static bool AllowedToEmail(UserAndConf user)
    {
        var canEmail = true; /// code checking if we can email the user
        if (canEmail)
        {
            return IssuegPermit(user.UserId);
        }
        else
        {
            return null
        }

    }
}

この解決策は、私が仕事の通常の日に行うものではありません。他の場所で問題が発生するためではなく、型破りなため(これまで見たことがない)、他の開発者に苦痛を与える可能性があります。

0
user1852503

汎用ネストクラスの場合=)

私はこれが古い質問であることを知っており、それはすでに受け入れられた回答を持っています。それにもかかわらず、この回答をマイニングする同様のシナリオを持っている可能性のあるグーグルスイマーにとっては、いくつかの助けを提供するかもしれません。

OPと同じ機能を実装する必要があるため、この質問に出くわしました。私の最初のシナリオでは this および this の回答がうまくいきました。それでも、ネストされたジェネリッククラスを公開する必要もありました。問題は、独自のクラスをジェネリックにすることなく、ジェネリックパラメーターを開いたデリゲート型フィールド(ファクトリフィールド)を公開できないことですが、これは明らかに私たちが望んでいることではないため、このようなシナリオに対する私の解決策を次に示します。

public class Foo
{
    private static readonly Dictionary<Type, dynamic> _factories = new Dictionary<Type, dynamic>();

    private static void AddFactory<T>(Func<Boo<T>> factory)
        => _factories[typeof(T)] = factory;

    public void TestMeDude<T>()
    {
        if (!_factories.TryGetValue(typeof(T), out var factory))
        {
            Console.WriteLine("Creating factory");
            RuntimeHelpers.RunClassConstructor(typeof(Boo<T>).TypeHandle);
            factory = _factories[typeof(T)];
        }
        else
        {
            Console.WriteLine("Factory previously created");
        }

        var boo = (Boo<T>)factory();
        boo.ToBeSure();
    }

    public class Boo<T>
    {
        static Boo() => AddFactory(() => new Boo<T>());

        private Boo() { }

        public void ToBeSure() => Console.WriteLine(typeof(T).Name);
    }
}

Booがプライベートコンストラクターを持つ内部ネストクラスとしてあり、親クラスにdynamicを利用してこれらのジェネリックファクトリーを持つディクショナリを保持します。したがって、TestMeDudeが呼び出されるたびに、FooはTのファクトリが既に作成されているかどうかを検索します。作成されていない場合は、ネストされたクラスの静的コンストラクタを呼び出してTを作成します。

テスト:

private static void Main()
{
    var foo = new Foo();

    foo.TestMeDude<string>();
    foo.TestMeDude<int>();
    foo.TestMeDude<Foo>();

    foo.TestMeDude<string>();

    Console.ReadLine();
}

出力は次のとおりです。

enter image description here

0
taquion