質問
各派生クラスをシングルトンにするように強制する抽象クラスを作成するにはどうすればよいですか? C#を使用します。
解決
時間チェックをコンパイルする前に、これは不可能です。ランタイムチェックを使用すると、これを行うことができます。きれいではありませんが、可能です。これが例です:
public abstract class Singleton
{
private static readonly object locker = new object();
private static HashSet<object> registeredTypes = new HashSet<object>();
protected Singleton()
{
lock (locker)
{
if (registeredTypes.Contains(this.GetType()))
{
throw new InvalidOperationException(
"Only one instance can ever be registered.");
}
registeredTypes.Add(this.GetType());
}
}
}
public class Repository : Singleton
{
public static readonly Repository Instance = new Repository();
private Repository()
{
}
}
他のヒント
シングルトンには静的アクセスが必要であり、強制できないため、それはうまくいきません。
singletonimplemention +例については、以下を参照してください。 C#でシングルトンパターンの実装
シングルトンとは、プライベートコンストラクターを持つことを意味します。しかし、あなたはプライベートメンバーを継承することができないことを知っています。 C ++にはテンプレートがあるため、テンプレートクラスからシングルトンを作成できます。 C#にはテンプレートがないため、必要なすべてのシングルトンに独自のプライベートコンストラクターを書く必要があります。
これを行うための(醜い)方法は次のとおりです。おそらく単純化され、改善される可能性がありますが、それは私の最初の進行です。
アイデアは、最初に基本クラスを一般的な抽象クラスにすることです(上記のコメントで述べたように)が、タイプパラメーターはベースクラス自体から派生するように制約されています。これにより、基本クラスは派生型のシングルトンインスタンスを処理できます。シングルトンクラスと同様に、すべての派生クラスを封印する必要があります。
次に、保護されたコンストラクターが許可されますが、修正されたシングルトンである特別なクラスのインスタンス、シングルトンキーを受け入れる必要があります。派生クラスはSingletonkeyクラスの定義にアクセスできますが、基本クラスは、唯一の許可されたインスタンス、したがってすべての導出されたオブジェクトの構築に対するプライベートコントロールを保持します。
第三に、基本クラスは派生クラスのコンストラクターを呼び出すことができる必要がありますが、これは少し注意が必要です。派生したキー付きコンストラクターに電話しようとすると、コンパイラは存在することが保証されていないため、文句を言います。解決策は、派生クラスが初期化する必要がある静的デリゲートを追加することです。したがって、派生クラスは、簡単な初期化方法を提供する必要があります。この初期化方法は、コードで初めてインスタンスにアクセスしようとする前に明示的に呼び出される必要があります。そうしないと、ランタイムエラーが生じます。
public abstract class Singleton<T> where T : Singleton<T>
{
protected Singleton(SingletonKey key) { }
private static SingletonKey _key;
private static SingletonKey Key
{
get
{
if (_key == null) SingletonKey.Initialize();
return _key;
}
}
protected class SingletonKey
{
private SingletonKey()
{
}
public static void Initialize()
{
if (_key == null)
{
_key = new SingletonKey();
}
}
}
protected static Func<SingletonKey, T> Creator;
private static T instance;
public static T Instance
{
get
{
if (instance == null) instance = Creator(Key);
return instance;
}
}
}
public class MySingleton : Singleton<MySingleton>
{
public string Name { get; set; }
public static void Initialize()
{
Creator = (key) => new MySingleton(key);
}
protected MySingleton(SingletonKey key) : base(key)
{
}
}
JavaまたはC#のクラスは「ファーストクラス」ではありません。クラスの静的部分は、サブクラスで継承したりオーバーライドしたりすることはできません。見る この答え 詳細については。さらに、メタクラスの概念はありません。
SmallTalkやRubyなどの言語では、新しいメタラスを定義できます Singleton
メソッドを定義します getInstance
. 。その後、定義できます ClassA
と ClassB
のインスタンスになります Singleton
メタラス。次に、両方のクラスがメソッドを自動的に公開します getInstance
インスタンスを作成するために使用できます objectA
また objectB
. 。それはクールではありませんか?まあ、実際には、あなたは頻繁にメタクラスを使用していません。シングルトンは実際には理にかなっている唯一の使用法であり、私が知っていることです。
同様の何かを達成しようとしたIEは、クラスのグループで共通のインターフェイスとシングルトンパターンを実施しようとしたと思います。これが私の解決策でした:
// Common interface of my singleton classes
public interface IMySingletonClass
{
string ValueGetter();
void ValueSetter(string value);
}
// Generic abstract base class
public abstract class Singleton<T>: IMySingletonClass
{
private static readonly object instanceLock = new object();
private static T instance; // Derived class instance
// Protected constructor accessible from derived class
protected Singleton()
{
}
// Returns the singleton instance of the derived class
public static T GetInstance()
{
lock (instanceLock)
{
if (instance == null)
{
instance = (T)Activator.CreateInstance(typeof(T), true);
}
return instance;
}
}
// IMySingletonClass interface methods
public abstract string ValueGetter();
public abstract void ValueSetter(string value);
}
// Actual singleton class
public class MySingletonClass : Singleton<MySingletonClass>
{
private string myString;
private MySingletonClass()
{
myString = "Initial";
}
public override string ValueGetter()
{
return myString;
}
public override void ValueSetter(string value)
{
myString = value;
}
}
簡単なテストです:
class Program
{
static void Main(string[] args)
{
MySingletonClass r1 = MySingletonClass.GetInstance();
Console.WriteLine("R1 value = {0}", r1.ValueGetter());
r1.ValueSetter("Changed through R1");
MySingletonClass r2 = MySingletonClass.GetInstance();
Console.WriteLine("R2 value = {0}", r2.ValueGetter());
Console.ReadKey();
}
}
基本的な「テンプレート」が必要な場合は、一般的な抽象シングルトンクラスから一般的なインターフェイスを簡単に削除できることに注意してください。
ここで、私のシングルトン継承の実装:
using System;
using System.Reflection;
namespace Mik.Singleton
{
class Program
{
static void Main()
{
//You can not create an instance of class directly
//Singleton1 singleton1 = new Singleton1();
Singleton1 singleton1 = Singleton1.Instance;
Singleton2 singleton2 = Singleton2.Instance;
Console.WriteLine(singleton1.Singleton1Text);
Console.WriteLine(singleton2.Singleton2Text);
Console.ReadLine();
}
}
public class SingletonBase<T> where T : class
{
#region Singleton implementation
private static readonly object lockObj = new object();
private static T _instance;
protected SingletonBase() { }
public static T Instance
{
get
{
if (_instance == null)
{
lock (lockObj)
{
if (_instance == null)
_instance = CreateInstance();
}
}
return _instance;
}
}
private static T CreateInstance()
{
ConstructorInfo constructor = typeof(T).GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null, new Type[0],
new ParameterModifier[0]);
if (constructor == null)
throw new Exception(
$"Target type is missing private or protected no-args constructor: {typeof(T).FullName}");
try
{
T instance = constructor.Invoke(new object[0]) as T;
return instance;
}
catch (Exception e)
{
throw new Exception(
"Failed to create target: type=" + typeof(T).FullName, e);
}
}
#endregion Singleton implementation
}
public class Singleton1 : SingletonBase<Singleton1>
{
private Singleton1() { }
public string Singleton1Text { get; } = "Singleton1Text value";
}
public class Singleton2 : SingletonBase<Singleton2>
{
private Singleton2() { }
public string Singleton2Text { get; } = "Singleton2Text value";
}
}