наследование абстрактного класса со статическим свойством в C #

Краткая версия:

У меня есть абстрактный класс A. У него есть метод, которому необходимо знать значение свойства статического класса, специфичного для каждого подкласса. Имя и тип одинаковы, только значение может быть уникальным для каждого подкласса.

Могу ли я определить это статическое свойство в базовом классе A, чтобы иметь к нему доступ с помощью методов, определенных в A, но сохраняя при этом значения свойств разных подклассов несвязанными?

Или как мне реализовать что-то подобное?


Полная версия:

Скажем, у меня есть абстрактный базовый класс для моделей данных. У него есть общедоступное свойство Id (Int32).

Я хотел бы реализовать конструктор в базовом классе, который генерирует новый идентификатор на основе последнего назначенного идентификатора для объектов подкласса.

Причина в том, что реальные идентификаторы назначаются автоматически базой данных, но каждый объект модели данных уже должен иметь уникальный идентификатор, когда он создается, но еще не записан в базу данных. Поскольку база данных назначает в качестве идентификатора только положительные целые числа, я планирую присвоить вновь созданным объектам модели данных временный уникальный отрицательный идентификатор. Как только объект будет записан, идентификатор изменится на настоящий.

Поскольку у меня довольно много разных классов модели данных, все они происходят от моего абстрактного базового класса, я подумал, что было бы хорошо включить туда эту функциональность, чтобы не дублировать ее. Но у каждого подкласса должен быть свой собственный счетчик, указывающий на следующий свободный отрицательный идентификатор, поскольку идентификаторы разных классов не связаны.

Поэтому мне нужно статическое свойство в каждом подклассе, хранящем последний назначенный временный идентификатор этого класса, но механизм его назначения всегда один и тот же и может быть реализован в конструкторе абстрактного базового класса. Однако я не могу получить доступ к свойству из базового класса, которое должно быть реализовано подклассами, что означает, что я должен определить его в базовом классе. Но будет ли это статическое свойство глобальным для всех подклассов, чего я не хочу?

Как я могу реализовать этот временный счетчик идентификаторов наиболее элегантным способом?

Пример упрощенного кода:

public abstract class ModelBase
{
    public Int32 Id { get; set; }
    protected static Int32 LastTempId { get; set; } = 0;

    public ModelBase()
    {
        Id = --LastTempId;
    }
}


public class Model1 : ModelBase
{
    public Model1 () : base ()
    {
        // do something model1-specific
    }
}

public class Model2 : ModelBase
{
    public Model2() : base()
    {
        // do something model2-specific
    }
}

Если я реализую это таким образом, я опасаюсь, что для обоих подклассов model1 и model2 унаследованное статическое свойство LastTempId будет одним и тем же экземпляром. Но мне нужен отдельный счетчик для каждого подкласса, но я все еще использую его в конструкторе базового класса.


person Byte Commander    schedule 27.04.2016    source источник
comment
Раньше я сталкивался с этой проблемой, когда мне требовались временные уникальные идентификаторы при создании объектов на клиенте, прежде чем отправлять их в веб-службу, которая предоставит им их настоящие идентификаторы. Можете ли вы где-нибудь собрать все используемые идентификаторы, а затем запросить тот, который еще не существует, когда вам понадобится новый?   -  person Jonesopolis    schedule 27.04.2016
comment
@Jonesopolis Я мог бы одновременно помещать несколько записей в базу данных, и это привело бы к путанице, если бы вдруг идентификаторы двух элементов поменялись местами, потому что один временный идентификатор становится реальным идентификатором другого ... Кроме того, я не обязательно всегда загружаю все данные ряды сразу.   -  person Byte Commander    schedule 27.04.2016
comment
Хранить динамические значения в статике - не лучшая идея. Я бы предпочел хранить все последние временные идентификаторы в файле, чтобы вы могли читать, увеличивать и обновлять его, если вы не делаете что-то в Интернете.   -  person jegtugado    schedule 27.04.2016
comment
Будет ли в каждом методе общая логика или вы просто хотите, чтобы каждый базовый класс имел этот статический метод, и логика была бы разной для каждой модели?   -  person D Stanley    schedule 27.04.2016
comment
@DStanley. Логика создания нового идентификатора такая же (уменьшение последнего временного идентификатора на 1), но значения должны быть специфичными для каждого подкласса. Если B, C и D являются подклассами A, и я создаю новые объекты типов B, C, C, B, D в этом порядке, я хочу, чтобы у них были временные идентификаторы -1 (B), -1 (C ), -2 (С), -2 (В), -1 (Г).   -  person Byte Commander    schedule 27.04.2016


Ответы (5)


Короткий ответ

Подклассы не могут иметь разные значения статического свойства, потому что статическое свойство является свойством класса, а не его экземпляров, и не наследуется.

Длинный ответ

Вы можете реализовать один счетчик абстрактного класса как статическое свойство и использовать его в одном конструкторе абстрактного класса.

РЕДАКТИРОВАТЬ: чтобы сохранить разные счетчики для каждого подкласса, вы можете использовать статический словарь, отображающий тип (подкласс) на счетчик.

public abstract class A<T>
{
    public static Dictionary<Type, int> TempIDs = new Dictionary<Type, int>();

    public int ID { get; set; }

    public A()
    {
        if (!TempIDs.ContainsKey(typeof(T)))
            TempIDs.Add(typeof(T), 0);

        this.ID = TempIDs[typeof(T)] - 1;

        TempIDs[typeof(T)]--;
    }
}

public class B : A<B>
{

    public string Foo { get; set; }

    public B(string foo)
        : base()
    {
        this.Foo = foo;
    }
}

public class C : A<C>
{
    public string Bar { get; set; }

    public C(string bar)
        : base()
    {
        this.Bar = bar;
    }
}

B b1 = new B("foo");
B b2 = new B("bar");

C c1 = new C("foo");
C c2 = new C("foo");

b1.ID будет -1, b2.ID будет -2, c1.ID будет -1 и c2.ID будет -2

person Marco Scabbiolo    schedule 27.04.2016
comment
Но теперь, если я сделаю еще один подкласс C из A и создам новый экземпляр этого, его идентификатор будет -3, верно? Я хочу, чтобы он был -1, как если бы для каждого подкласса был отдельный счетчик. - person Byte Commander; 27.04.2016
comment
Я изменил код, чтобы поддерживать один счетчик на подкласс - person Marco Scabbiolo; 27.04.2016
comment
Приятно видеть, как ваше последнее редактирование представило мой собственный подход к ответу с использованием параметра универсального типа и всего словаря, где ключи являются типами сущностей: D Кстати, ответ не тот, но, похоже, я бы отдал должное идея, если бы я взял что-нибудь у другого отвечающего ... - person Matías Fidemraizer; 27.04.2016
comment
Я не взял идею из вашего ответа, у меня она была сама, иначе я бы отдал вам должное. На самом деле я не читал твой ответ. - person Marco Scabbiolo; 27.04.2016

Во-первых, мое скромное мнение заключается в том, что сущности не должны нести ответственность за присвоение собственного уникального идентификатора. Держите четкое разделение проблем.

В этой игре должен быть другой игрок, который должен назначить эти временные уникальные идентификаторы (отрицательные или положительные целые числа).

Обычно этот так называемый другой проигрыватель является реализацией шаблона проектирования репозитория который отвечает за перевод домена (ваших моделей) в окончательное представление ваших данных и наоборот.

Обычно в репозитории есть метод добавления объектов . И это должен быть момент, когда вы устанавливаете эти временные идентификаторы:

public void Add(Some some)
{
    some.Id = [call method here to set the whole id];
}

И большинство реализаций репозитория для каждой сущности.

  • CustomerRepository
  • InvoiceRepository
  • ...

... но это не мешает вам определить базовый класс репозитория, который мог бы реализовать то, что может быть общим при обработке некоторых типов сущностей:

   public interface IRepository<TEntity> where TEntity : EntityBase
   {
         // Other repository methods should be defined here
         // but I just define Add for the convenience of this 
         // Q&A
         void Add(TEntity entity);
   }


   public class Repository<TEntity> : IRepository<TEntity>
          where TEntity : EntityBase
   {
         public virtual void Add(TEntity entity)
         {
              entity.Id = [call method here to set the whole id];
         }
   }

... и теперь любой класс, производный Repository<TEntity>, сможет сгенерировать временный идентификатор для своих специализированных сущностей:

   public class CustomerRepository : Repository<Customer> { }
   public class InvoiceRepository : Repository<Invoice> { }

Как вы могли бы реализовать уникальный и временный идентификатор объекта как часть абстрактного класса репозитория и иметь возможность сделать это для каждого конкретного типа объекта?

Используйте словарь для хранения последнего присвоенного идентификатора каждой сущности, реализующего свойство для Repository<TEntity>:

public Dictionary<Type, int> EntityIdentifiers { get; } = new Dictionary<Type, int>();

... и способ уменьшения следующего временного идентификатора:

private static readonly object _syncLock = new object();

protected virtual void GetNextId()
{
     int nextId;

     // With thread-safety to avoid unwanted scenarios.
     lock(_syncLock)
     {
          // Try to get last entity type id. Maybe the id doesn't exist
          // and out parameter will set default Int32 value (i.e. 0).
          bool init = EntityIdentifiers.TryGetValue(typeof(TEntity), out nextId);
          // Now decrease once nextId and set it to EntityIdentifiers
          nextId--;

          if(!init)
               EntityIdentifiers[typeof(TEntity)] = nextId;
          else
               EntityIdentifiers.Add(typeof(TEntity), nextId);
     }

     return nextId;    
}

Наконец, ваш Add метод может выглядеть следующим образом:

public virtual void Add(TEntity entity)
{
     entity.Id = GetNextId();
}
person Matías Fidemraizer    schedule 27.04.2016

Один из способов - это отражение, но оно требует времени выполнения и подвержено ошибкам во время выполнения. Как уже упоминалось, вы не можете заставить наследовать классы повторно объявить какое-то статическое поле и иметь возможность использовать это поле в классе-предке. Поэтому я считаю, что необходима минимальная избыточность кода: каждый наследующий класс должен предоставлять собственный генератор ключей. Этот генератор, конечно, можно держать в статическом поле класса.

(Обратите внимание, что это не обязательно является потокобезопасным.)

class KeyGenerator
{
    private int _value = 0;

    public int NextId()
    {
        return --this._value;
    }
}

abstract class ModelBase
{
    private KeyGenerator _generator;

    public ModelBase(KeyGenerator _generator)
    {
        this._generator = _generator;
    }

    public void SaveObject()
    {
        int id = this._generator.NextId();
        Console.WriteLine("Saving " + id.ToString());
    }
}

class Car : ModelBase
{
    private static KeyGenerator carKeyGenerator = new KeyGenerator();

    public Car()
        : base(carKeyGenerator)
    {
    }
}

class Food : ModelBase
{
    private static KeyGenerator foodKeyGenerator = new KeyGenerator();

    public Food()
        : base(foodKeyGenerator)
    {
    }
}

class Program
{
    static void Main(string[] args)
    {
        Food food1 = new Food();
        Food food2 = new Food();
        Car car1 = new Car();
        food1.SaveObject();
        food2.SaveObject();
        car1.SaveObject();
    }
}

Это производит:

Saving -1
Saving -2
Saving -1
person Kuba Wyrostek    schedule 27.04.2016

Просто создайте GUID для каждого объекта, прежде чем он будет добавлен в вашу базу данных. У вас может быть флаг isAdded, который сообщает, что объект должен называться GUID, или очистить GUID после добавления объекта. С GUID вам никогда не придется беспокоиться о столкновении двух объектов. Также это устраняет необходимость в отдельных идентификаторах для каждого подкласса. Я бы не стал повторно использовать одно и то же свойство для двух состояний, как вы предлагаете.

https://msdn.microsoft.com/en-us/library/system.guid(v=vs.110).aspx

person bodangly    schedule 27.04.2016

Что ж, статические классы не наследуются, так что это out, m, и вы не можете заставить подклассы реализовывать статический метод, так что это тоже исключено.

Вместо того, чтобы помещать этот метод в сам класс, почему бы не иметь базовый интерфейс, который вы можете реализовать. Затем у вас может быть метод экземпляра, который может быть абстрактным:

public interface IDataModelFactory<T> where T:ModelBase
{
    int GetLastTempId();
}

public Model1Factory : IDataModelFactory<Model1>
{
    public int GetLastTempId()
    {
        // logic for Model1 
    }
}

public Model2Factory : IDataModelFactory<Model2>
{
    public int GetLastTempId()
    {
        // logic for Model2
    }
}

Или, если логика общая для всех классов, создайте абстрактный базовый класс с (или без) интерфейсом:

public DataModelFactory<T> : IDataModelFactory<T>
{
    public virtual int GetLastTempId()
    {
        // common logic
    }

    // other common logic
}

Вы даже можете сделать фабрики одиночными, чтобы вам не приходилось постоянно создавать экземпляры, и они могут даже быть подклассами классов модели, чтобы они были тесно связаны.

В качестве примечания: если вы не уверены, какими будут отношения наследование / интерфейс, я часто нахожу, что быстрее начать с повторного использования копирования / вставки и рефакторинга вашего кода, чтобы ввести базовые классы и интерфейсы. Таким образом вы узнаете, что такое общий код, и сможете преобразовать его в общие методы. В противном случае у вас возникнет соблазн попробовать поместить все в базовый класс и использовать переключатели или другие конструкции для изменения логики на основе производного типа.

person D Stanley    schedule 27.04.2016
comment
Извините, я понял это после публикации вопроса и отредактировал его: мне не нужен абстрактный метод, все, что мне нужно, это свойство, которое является статическим для подклассов, но доступно для использования из конструктора абстрактного базового класса. - person Byte Commander; 27.04.2016
comment
@ByteCommander Как я уже сказал, это невозможно - статические методы привязаны к определенному классу. Они не наследуются, не могут быть виртуальными, и вы не можете заставить подклассы реализовать статический метод. Я предлагаю альтернативу, которая работает. - person D Stanley; 27.04.2016