Сразу говорю — недостатки использования таких классов в качестве синглетонов мне известны, вопрос не о них.
Есть такой класс:
public class Singleton<T> where T : Singleton<T>, new()
{
protected static readonly object lock_obj = new Object();
protected static T _Instance = null;
public static T Instance
{
get
{
if (_Instance == null)
{
lock (lock_obj)
{
if (_Instance == null)
{
_Instance = new T(); //ВОТ ЗДЕСЬ ПРОБЛЕМА!!! (см. далее)
}
}
}
return _Instance;
}
}
}
Наследуюсь от него:
public class DB:Singleton<DB>
{
protected string _ConnectionString = null;
public DB()
{
_ConnectionString = ...;
}
//+Еще много-много-много методов
}
Теперь требуется создать другой класс DB (с другой _ConnectionString — все остальное то же самое):
public class UserDB:DB
{
public UserDB()
{
_ConnectionString = ...; //ЭТОТ КОД НИКОГДА НЕ ВЫПОЛНЯЕТСЯ!!!
}
}
Т.е. происходит следующее:
1. Вызываю неявно создание экземпляра UserDB:
IDataReader dr=UserDB.ExecuteReader....;
2. В классе Singleton<T> вызывается
_Instance = new T();
НО <T> здесь — это тип DB, а не UserDB как мне надо (хотя вызов идет именно от класса UserDB)
Я понимаю, почему так происходит, подскажите как можно это реализовать.
public class DB
{
}
public class UserDB : DB
{
}
public class SingleDB<T> : Singleton<T> where T : DB, new()
{
}
public class SingleUserDB : SingleDB<UserDB>
{
}
SingleUserDB.Instance.SomeMethod();
.
Das Reich der Freiheit beginnt da, wo die Arbeit aufhört. (c) Karl Marx
Спасибо за действительно верное решение, но в моем случае оно не подходит.
Попробую объяснить чего я хочу по подробнее. Я делаю три базовых синглетона (обычный, уникальный в пределах сессии, уникальный в пределах HTTP-запроса): Singleton<T>, HttpSessionSingleton<T> и HttpRequestSingleton<T>. Они отличаются только способом выдачи Instance: для Singleton<T> выдается статичный экземпляр (исходник класса приведен выше); для HttpSessionSingleton<T> выдается экземпляр хранящийся в ASP.Net сессии и т.п.
Я хочу избавится от ".Instance." в коде "SingleUserDB.Instance.SomeMethod()", чтобы этот код превратился в "SingleUserDB.SomeMethod()".
Данная проблема стоит для меня скорее в чисто теоретических целях (просто я хочу, чтобы код выглядел красиво), на практике конечно можно пользоваться ".Instance.".
Основная идея моего проектирования данного куска — нужно чтобы код выглядел так:
public class CurrentUser:HttpSessionSingleton<CurrentUser>
{
private string _Name=null;
public CurrentUser()
{
_Name="имя";
}
public static string Name
{
return Instance._Name;
}
}
Теперь в любом месте программы (без всякого создания CurrentUser-а, операций с сессией и т.п.) мы можем написать:
string s=CurrentUser.Name;
При этом класс CurrentUser должен наследоваться как от синглетона (чтобы автоматом создаваться — Instance), так и содержать Instance, имеющего тип "самого себя" (чтобы взять базовую функциональность -Name).
Все отлично работает до тех пор, пока мы не наследуем CurrentUser:
public class VIPCurrentUser:CurrentUser
{
public VIPCurrentUser()
{
_Name="какое-то другое имя"; //этот кусок никогда не выполнится, вместо него будет неявно вызываться конструктор CurrentUser()
}
}
Здравствуйте, ksg71, Вы писали: K>Вот так будет работать K>
K>public class VipCurrentUser:HttpSessionSingleton<VipCurrentUser>
K>
Да, но тогда весь функционал работы с пользователем (поле UserName например), надо: либо засовывать в HttpSessionSingleton, тогда HttpSessionSingleton напрочь теряет свою универсальность; либо дублировать в классах VipCurrentUser и CurrentUser, т.к. VipCurrentUser с CurrentUser никак не связан.
Тут бы помогло множественное наследование, но его нет.
K>только метод инициализации инстанса в K>
K>public class CurrentUser:HttpSessionSingleton<CurrentUser>
K>
K>должен создавать экземпляры различных юзеров (Vip или еще каких), в зависимости от каких либо условий
С этим как раз проблем нет:
public class HttpSessionSingleton<T> where T : HttpSessionSingleton<T>, new()
{
public static T Instance
{
get
{
if (HttpContext.Current.Session[SessionKey] == null)
{
lock (lock_obj)
{
if (HttpContext.Current.Session[SessionKey] == null)
{
HttpContext.Current.Session[SessionKey] = new T();
}
}
}
return current;
}
}
}
то есть создается именно тот инстанс, тип которого был передан в дженерик. Получается как бы, что класс наследуется и от синглетрона и от самого себя (через свойство инстанс)
Здравствуйте, iukpshl, Вы писали:
I> if (_Instance == null) I> { I> _Instance = new T(); //ВОТ ЗДЕСЬ ПРОБЛЕМА!!! (см. далее) I> }
Слушай — может быть я нещадно туплю, но по-моему ты неверно понял идею реализации синглтона. У меня НЕ ВЫЗЫВАЕТСЯ выделенный участо кода. Поясни — как при вызове обычного конструктора посредством UserDB.ExecuteReader, ты получаешь синглтон? По-моему синглтоном и не пахнет.
I>Т.е. происходит следующее: I>1. Вызываю неявно создание экземпляра UserDB: I>
I>IDataReader dr=UserDB.ExecuteReader....;
I>
I>2. В классе Singleton<T> вызывается I>
_Instance = new T();
I>НО <T> здесь — это тип DB, а не UserDB как мне надо (хотя вызов идет именно от класса UserDB)
И ничего удивительного. Ты же сделал DB наследником закрытого класса Singleton<DB> — следовательно в месте _Instance = new T(); будет подставлен вызов именно DB. Дженерик и знать ничего не знает что это наследник UserDB. Не поможет даже операция typeof() — все равно вернет DB. Чем инстанцировал шаблон — то и получишь.
I>Я понимаю, почему так происходит, подскажите как можно это реализовать.
Я бы реализовал это так:
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace Singleton
{
public class Singleton<T>
where T : class, new()
{
protected static readonly object syncRoot = new Object();
protected static T _Instance = null;
public static T Instance
{
get
{
lock (syncRoot)
{
if (_Instance == null)
{
_Instance = CreateInstance();
}
}
return _Instance;
}
}
private static T CreateInstance()
{
ConstructorInfo[] ctorArray = typeof(T).GetConstructors(BindingFlags.Default |
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach (ConstructorInfo ctorInfo in ctorArray)
{
if (ctorInfo.GetParameters().Length == 0)
{
return ctorInfo.Invoke(null) as T;
}
}
return default(T);
}
}
public class DB
{
private string _ConnectionString = null;
public DB()
{
_ConnectionString = "Connection string DB";
}
//+Еще много-много-много методовpublic string ConnectionString
{
get
{
return _ConnectionString;
}
set
{
_ConnectionString = value;
}
}
}
public class UserDB : DB
{
public UserDB()
{
ConnectionString = "Connection string User DB"; //ЭТОТ КОД НИКОГДА НЕ ВЫПОЛНЯЕТСЯ!!!
}
}
class Program
{
static void Main(string[] args)
{
DB db = Singleton<DB>.Instance;
Console.WriteLine(db.ConnectionString);
UserDB userDB = Singleton<UserDB>.Instance;
Console.WriteLine(userDB.ConnectionString);
DB db2 = Singleton<DB>.Instance;
if (Object.ReferenceEquals(db, db2))
{
Console.WriteLine("That's real singleton");
}
Console.ReadLine();
}
}
}
Маленькая поправка. Подвел копипаст из рабочего проекта. Поскольку я указал в ограничении для параметра класс new(), то класс Singleton упроститься до такого состояния:
public class Singleton<T>
where T : class, new()
{
protected static readonly object syncRoot = new Object();
protected static T _Instance = null;
public static T Instance
{
get
{
lock (syncRoot)
{
if (_Instance == null)
{
_Instance = new T();
}
}
return _Instance;
}
}
}
Здравствуйте, DangerRSDN, Вы писали:
DRS>Здравствуйте, iukpshl, Вы писали:
} DRS>Слушай — может быть я нещадно туплю, но по-моему ты неверно понял идею реализации синглтона. У меня НЕ ВЫЗЫВАЕТСЯ выделенный участо кода. Поясни — как при вызове обычного конструктора посредством UserDB.ExecuteReader, ты получаешь синглтон? По-моему синглтоном и не пахнет.
UserDB.ExecuteReader() — это обычный статичный метод, выглядит вот так:
public static IDataReader ExecuteReader(...)
{
return Instance.ExecuteReader(...)
}
При первом обращении к Instance вызывается конструктор
И потом, я же специально написал: Сразу говорю — недостатки использования таких классов в качестве синглетонов мне известны, вопрос не о них.
DRS>И ничего удивительного. Ты же сделал DB наследником закрытого класса Singleton<DB> — следовательно в месте _Instance = new T(); будет подставлен вызов именно DB. Дженерик и знать ничего не знает что это наследник UserDB. Не поможет даже операция typeof() — все равно вернет DB. Чем инстанцировал шаблон — то и получишь.
И об этом я сразу написал Я понимаю, почему так происходит, подскажите как можно это реализовать.
Т.е. вопрос был в том как по другому можно это реализовать.
DRS>Я бы реализовал это так: DRS>
DRS> ....
DRS> DB db = Singleton<DB>.Instance;
DRS> Console.WriteLine(db.ConnectionString);
DRS> ....
DRS>
То есть получается "Singleton<DB>.Instance.ConnectionString" — а мне нужно "Singleton<DB>.ConnectionString" (см. мои поздние сообщения)
Здравствуйте, ыукпшл, Вы писали:
Ы>То есть получается "Singleton<DB>.Instance.ConnectionString" — а мне нужно "Singleton<DB>.ConnectionString" (см. мои поздние сообщения)
Увы — но наверное так не получиться. Или, как вариант — разные классы. Т.е. DB: Singleton<DB> и UserDB: Singleton<UserDB>. Другого выхода я не вижу.