繁体   English   中英

C#中使用静态属性继承抽象类

[英]inheritance of abstract class with static property in C#

简洁版本:

我有一个抽象类A.它有一个方法需要知道特定于每个子类的静态类属性的值。 名称和类型相同,只是每个子类的值可以是唯一的。

我可以在基类A中定义此静态属性,以便能够使用A中定义的方法访问它,但是保持不同子类的属性值不相关吗?

或者我将如何实现这样的东西?


长版:

假设我有一个数据模型的抽象基类。 它有一个公共财产Id (Int32)。

我想在基类中实现一个构造函数,该构造函数基于最后为子类的对象分配的ID生成新的ID。

原因是实际 ID是由数据库自动分配的,但是每个数据模型对象在构造时必须具有唯一的ID而尚未写入数据库。 由于数据库仅将正整数分配为ID,因此我的计划是为新创建的数据模型对象分配一个临时的唯一否定ID。 一旦对象被写入,ID就会变为真实的ID。

由于我有很多不同的数据模型类都来自我的抽象基类,我认为将那些功能包含在那里以便不重复它会很好。 但是每个子类必须有自己的计数器,指向下一个自由否定ID,因为不同类的ID是不相关的。

所以我需要在每个子类中存储一个静态属性,存储该类的最后一个分配的临时ID,但是分配它的机制总是相同的,并且可以实现到抽象基类的构造函数中。 但是,我无法从必须由子类实现的基类访问属性,这意味着我必须在基类中定义它。 但是这个静态属性对所有子类都是全局的,这不是我想要的吗?

如何以最优雅的方式实现此临时ID计数器?

简化的代码示例:

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
    }
}

如果我像这样实现它,我担心对于子类model1model2 ,继承的静态属性LastTempId将是同一个实例。 但我想为每个子类创建一个单独的计数器,同时仍然在基类构造函数中使用它。

简短的回答

子类不能具有静态属性的不同值,因为静态属性是类的属性,而不是它的实例,并且它不是继承的。

答案很长

您可以在抽象类上实现一个单独的计数器作为静态属性,并使用它的一个抽象类的构造函数。

编辑 :要为每个子类保存不同的计数器,您可以使用静态字典将Type(子类)映射到计数器。

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-1b2.ID-2c1.ID-1c2.ID-2

首先,我的拙见是实体不应该负责分配自己的唯一标识符。 保持明确的关注点分离。

该游戏中应该有另一个玩家应该分配那些临时唯一标识符(如果它们是负整数或正整数)。

通常,所谓的其他播放器是存储库设计模式的实现,它负责将域(您的模型)转换为数据的确定表示,反之亦然。

通常, 存储库具有添加对象的方法。 这应该是您设置这些临时标识符的点:

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>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();
}

一种方法是反射,但它需要运行时并且容易出现运行时错误。 正如其他人提到的:你不能强制继承类来重新声明一些静态字段,并且能够在祖先类中使用这个字段。 所以我认为最小的代码冗余是必要的:每个继承类都应该提供它自己的密钥生成器。 这个发生器当然可以保存在类的静态区域中。

(注意这不一定是线程安全的。)

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

只需在每个对象添加到数据库之前为其生成GUID。 您可以使用isAdded标志告诉您该对象应该被引用为GUID,或者在添加对象后清除GUID。 使用GUID,您永远不必担心两个对象会发生冲突。 此外,它还避免了每个子类需要单独的ID。 我不会像你提议的那样为两个州重复使用相同的属性。

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

好吧,静态类不是继承的,所以这就是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
}

你甚至可以让工厂单身,所以你不必一直创建实例,它们甚至可以是模型类的子类,因此它们是紧密相连的。

作为旁注,如果您不确定继承/接口关系是什么,我经常会发现复制/粘贴重用更快,重构代码以引入基类和接口。 通过这种方式,您可以了解常用代码的含义,并将其重构为常用方法。 否则,您很想尝试将所有内容放在基类中,并使用开关或其他构造来根据派生类型更改逻辑。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM