ネストされたクラスのメンバーは、それを囲むクラスからはアクセスできるが、他のクラスからはアクセスできないように指定することはできますか?
これが問題の説明です(もちろん、実際のコードはもう少し複雑です...):
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
インターフェースに行きました(私の質問の最後の要件にもかかわらず...)
クラスが複雑すぎない場合は、公開されているインターフェースを使用して実際の実装クラスをプライベートにするか、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);
}
}
これにより、遅いリフレクションに頼ったり、追加のクラス/インターフェースを導入したりする必要なく、目的の可視性レベルが得られます。
実際、クライアントコードの変更やインターフェイスの作成を含まない、この問題に対する完全で単純な解決策があります。
このソリューションは、ほとんどの場合、インターフェイスベースのソリューションよりも実際に高速で、コーディングが簡単です。
public class Journal
{
private static Func<object, JournalEntry> _newJournalEntry;
public class JournalEntry
{
static JournalEntry()
{
_newJournalEntry = value => new JournalEntry(value);
}
private JournalEntry(object value)
{
...
JournalEntry
をprivatenested typeにします。パブリックメンバーは、それを含む型に対してのみ表示されます。
public class Journal
{
private class JournalEntry
{
}
}
JournalEntry
オブジェクトを他のクラスで使用できるようにする必要がある場合は、パブリックインターフェイスを介してそれらを公開します。
public interface IJournalEntry
{
}
public class Journal
{
public IEnumerable<IJournalEntry> Entries
{
get { ... }
}
private class JournalEntry : IJournalEntry
{
}
}
より単純な方法は、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);
}
}
この場合、次のいずれかを行うことができます。
JournalEntry
クラスをリファクタリングしてパブリックインターフェイスを使用し、実際のJournalEntry
クラスをプライベートまたは内部にします。次に、実際の実装が非表示になっている間、インターフェイスをコレクションに公開できます。上記の有効な修飾子として内部を説明しましたが、要件によっては、プライベートの方が適している場合があります。
編集:申し訳ありませんが、プライベートコンストラクターについて言及しましたが、質問のこの点についてはすでに扱っています。正しくお読みいただけなかったことをお詫び申し上げます。
グリズリーが提案した解決策は、ネストされたクラスをどこか他の場所に作成することを少し難しくしますが、不可能ではありません。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
}
}
}
この解決策は、私が仕事の通常の日に行うものではありません。他の場所で問題が発生するためではなく、型破りなため(これまで見たことがない)、他の開発者に苦痛を与える可能性があります。
汎用ネストクラスの場合=)
私はこれが古い質問であることを知っており、それはすでに受け入れられた回答を持っています。それにもかかわらず、この回答をマイニングする同様のシナリオを持っている可能性のあるグーグルスイマーにとっては、いくつかの助けを提供するかもしれません。
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();
}
出力は次のとおりです。