Вопрос по generic-ам
От: iukpshl  
Дата: 11.05.07 07:34
Оценка:
Сразу говорю — недостатки использования таких классов в качестве синглетонов мне известны, вопрос не о них.
Есть такой класс:
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)

Я понимаю, почему так происходит, подскажите как можно это реализовать.
Re: Вопрос по generic-ам
От: ksg71 Германия  
Дата: 11.05.07 07:53
Оценка:
Здравствуйте, iukpshl, Вы писали:

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
Re[2]: Вопрос по generic-ам
От: ыукпшл Россия  
Дата: 11.05.07 09:02
Оценка:
Здравствуйте, ksg71, Вы писали:

K>.....


Спасибо за действительно верное решение, но в моем случае оно не подходит.

Попробую объяснить чего я хочу по подробнее. Я делаю три базовых синглетона (обычный, уникальный в пределах сессии, уникальный в пределах 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()
    }
}
Re[3]: Вопрос по generic-ам
От: ksg71 Германия  
Дата: 11.05.07 09:25
Оценка:
Здравствуйте, ыукпшл, Вы писали:

Вот так будет работать
public class VipCurrentUser:HttpSessionSingleton<VipCurrentUser>


только метод инициализации инстанса в
public class CurrentUser:HttpSessionSingleton<CurrentUser>


должен создавать экземпляры различных юзеров (Vip или еще каких), в зависимости от каких либо условий
Das Reich der Freiheit beginnt da, wo die Arbeit aufhört. (c) Karl Marx
Re[4]: Вопрос по generic-ам
От: ыукпшл Россия  
Дата: 11.05.07 09:49
Оценка:
Здравствуйте, 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;
            }
        }
}

то есть создается именно тот инстанс, тип которого был передан в дженерик. Получается как бы, что класс наследуется и от синглетрона и от самого себя (через свойство инстанс)
Re: Вопрос по generic-ам
От: DangerRSDN Россия http://danger-world.livejournal.com/
Дата: 11.05.07 10:16
Оценка:
Здравствуйте, 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();
    }
  }
}
Re[2]: Вопрос по generic-ам
От: DangerRSDN Россия http://danger-world.livejournal.com/
Дата: 11.05.07 10:27
Оценка:
Маленькая поправка. Подвел копипаст из рабочего проекта. Поскольку я указал в ограничении для параметра класс 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;
      }
    }
  }
Re[2]: Вопрос по generic-ам
От: ыукпшл Россия  
Дата: 11.05.07 10:35
Оценка:
Здравствуйте, 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" (см. мои поздние сообщения)

В любом случае, спасибо за сообщение
Re[3]: Вопрос по generic-ам
От: ыукпшл Россия  
Дата: 11.05.07 10:37
Оценка:
Здравствуйте, DangerRSDN, Вы писали:
DRS>
DRS>  public class Singleton<T>
DRS>    where T : class, new()
DRS>  {
DRS>    protected static readonly object syncRoot = new Object();
DRS>    protected static T _Instance = null;

DRS>    public static T Instance
DRS>    {
DRS>      get
DRS>      {
DRS>        lock (syncRoot)
DRS>        {
DRS>          if (_Instance == null)
DRS>          {
DRS>            _Instance = new T();
DRS>          }
DRS>        }

DRS>        return _Instance;
DRS>      }
DRS>    }
DRS>  }
DRS>

один в один — код как у меня
Re[3]: Вопрос по generic-ам
От: DangerRSDN Россия http://danger-world.livejournal.com/
Дата: 11.05.07 12:44
Оценка:
Здравствуйте, ыукпшл, Вы писали:

Ы>То есть получается "Singleton<DB>.Instance.ConnectionString" — а мне нужно "Singleton<DB>.ConnectionString" (см. мои поздние сообщения)

Увы — но наверное так не получиться. Или, как вариант — разные классы. Т.е. DB: Singleton<DB> и UserDB: Singleton<UserDB>. Другого выхода я не вижу.
Re: Вопрос по generic-ам
От: Дьяченко Александр Россия  
Дата: 13.05.07 04:14
Оценка:
Здравствуйте, iukpshl!

Если все еще хочется, то я накидал вариант, хотя конечно прямым я бы его называть не стал (здесь).
... << RSDN@Home 1.2.0 alpha rev. 678>>
Re: Вопрос по generic-ам
От: twirpx  
Дата: 21.05.07 15:04
Оценка:
Здравствуйте, iukpshl, Вы писали:

I>Я понимаю, почему так происходит, подскажите как можно это реализовать.


public class DB<T> : Singleton<T> where T : DB<T> {
}
public class UserDB : DB<UserDB> {
}


Так пойдет?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[2]: Вопрос по generic-ам
От: ыукпшл Россия  
Дата: 25.05.07 12:15
Оценка:
Здравствуйте, Дьяченко Александр, Вы писали:

ДА>Если все еще хочется, то я накидал вариант, хотя конечно прямым я бы его называть не стал (здесь).


Работает, хотя вариант и вправду кривоват, поскольку приходится писать врапперы для методов. Спасибо, буду разбираться...
Re[2]: Вопрос по generic-ам
От: ыукпшл Россия  
Дата: 25.05.07 12:50
Оценка:
Здравствуйте, twirpx, Вы писали:

T>Здравствуйте, iukpshl, Вы писали:


I>>Я понимаю, почему так происходит, подскажите как можно это реализовать.


T>
T>public class DB<T> : Singleton<T> where T : DB<T> {
T>}
T>public class UserDB : DB<UserDB> {
T>}
T>


T>Так пойдет?


Работает, супер, то, что надо
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.