简体   繁体   English

将数据与基本抽象类型的具体子类相关联

[英]Associate data with concrete subclasses from a base abstract type

I've stumbled upon the need to do this a few times recently with some lower level framework type stuff and I'd like to see if there is a better/cleaner way to accomplish this, ie if I'm missing something obvious or clever, like the time I discovered [ThreadStatic] to replace dictionary lookups against thread IDs for associating data with Threads. 我最近偶然发现需要做一些低级框架类型的东西,我想看看是否有更好/更清晰的方法来实现这一点,即如果我遗漏了明显或聪明的东西就像我发现[ThreadStatic]用线程ID替换字典查找以将数据与线程相关联的时间。

I have a base abstract class, lets call it Entity . 我有一个基础抽象类,让我们称之为Entity Every Entity needs to perform a set of initialization actions in the constructor that depends on the actual concrete class being instantiated. 每个Entity需要在构造函数中执行一组初始化操作,这些操作取决于实例化的实际具体类。 Is there a way I can accomplish this without doing a dictionary lookup and calling this.GetType() ? 有没有办法我可以在不进行字典查找并调用this.GetType()情况下完成此操作?

Here is some code similar to what I have now: 这是一些类似于我现在的代码:

public abstract class Entity
{
    private static Dictionary<Type, Action<EntityData>> _initActions = new Dictionary<Type, Action<EntityData>>();

    private EntityData _data = new EntityData();

    protected Entity()
    {
        _initActions[this.GetType()].Invoke(_data);
    }
}

public class Employee : Entity
{
    public string Name { get; set; }
}

public class Manager : Employee
{
    public List<Employee> Subordinates { get; set; }
}

The Employee constructor and Manager constructor need to initialize their _data fields differently as they are different types. Employee构造函数和Manager构造函数需要以不同的类型初始化它们的_data字段。 The _initActions collection gets initialized in another method prior to any instances being new'd up, which I don't think has any significance on this discussion. _initActions集合在任何实例新建之前都会在另一个方法中初始化,我认为这个讨论没有任何意义。

I want usage of the class to remain as simple as possible for the user of the framework, so I can't use strange hacks like requiring users to override an Init method in each concrete type in some peculiar or unintuitive way. 我希望类的使用对于框架的用户保持尽可能简单,所以我不能使用奇怪的黑客,比如要求用户以某种特殊或不直观的方式覆盖每种具体类型的Init方法。

Generics almost work, in the sense that I could do something like Entity<TEntity> to get a TEntity specific static field to store the init method if I didn't have any inheritance, but inheritance needs to be supported so I would need a dictionary of all init methods for the subclasses of TEntity anyway. 泛型几乎可以工作,从某种意义上说,我可以做一些像Entity<TEntity>来获取一个特定于TEntity的静态字段来存储init方法,如果我没有任何继承,但是需要支持继承,所以我需要一个字典无论如何,对于TEntity的子类的所有init方法。

This code runs in some pretty low level database engine type scenarios in tight loops with 1m iterations, so getting rid of the dictionary lookup does provide some significant speedups in certain situations (tested by replacing with a hacky Init override implementation). 此代码在一些非常低级别的数据库引擎类型场景中以1m迭代的紧密循环运行,因此摆脱字典查找确实在某些情况下提供了一些显着的加速(通过替换为hacky Init覆盖实现来测试)。

Any ideas? 有任何想法吗?

EDIT: 编辑:

I want to make a few things clear. 我想说清楚一些事情。 The entity engine automatically sets up _initAction to do what it needs to to initialize its _data container. 实体引擎自动设置_initAction以执行初始化其_data容器所需的操作。 The "user" of the library knows nothing about this process and doesn't need to. 库的“用户”对此过程一无所知,也不需要。 All I was inquiring about is a way to avoid a dictionary lookup to get type-specific runtime information from a base class, but that may not be possible. 我所询问的是一种避免字典查找以从基类获取特定于类型的运行时信息的方法,但这可能是不可能的。

Yes, this is micro-optimization, but we have tested this with real queries and gotten 15-20% query time reductions on some queries that need to instantiate large datasets. 是的,这是微优化,但我们已经使用真实查询对此进行了测试,并且对需要实例化大型数据集的一些查询的查询时间减少了15-20%。

The faster code looked like this: 更快的代码看起来像这样:

public class Employee : Entity
{
    private static EntityInitializer _initMethod = Entity.GetInitMethod(typeof(Employee));

    public string Name { get; set; }

    public Employee()
    {
        _initMethod.Invoke(this);
    }
}

This way, the dictionary lookup is done once for the Employee type. 这样,对Employee类型执行一次字典查找。 It's not horrible, but it requires a) boilerplate in every single class, which I don't like and b) slightly error prone as you have to match up the type parameter with the current class otherwise funky things happen, kind of like when you type in the wrong owner class name for a dependency property in WPF. 它并不可怕,但它需要a)每个类中的样板,我不喜欢和b)略有错误,因为你必须将类型参数与当前类匹配,否则会发生时髦的事情,有点像你的时候在WPF中为依赖项属性键入错误的所有者类名称。 Kinda sometimes works, but then wierd bugs pop up and its hard to trace back. 有时候有点工作,但随后会出现奇怪的错误并且难以追溯。

What it comes down to is this: is there was a better way to attach arbitrary runtime data to a Type besides using a Dictionary, considering that all these types that will have this data attached to them all implement a common base class? 它归结为:除了使用Dictionary之外,还有更好的方法将任意运行时数据附加到Type,考虑到所有这些附加了这些数据的类型都实现了公共基类吗?

Could you not just create a ctor that you pass the type to? 你能不能创建一个你传递类型的ctor?

    protected Entity(Type type)
    {
        _initActions[type].Invoke(_data);
    }
}

public class Employee : Entity
{
    private static Type mytype = typeof(Employee);
    public string Name { get; set; }
    public Employee(): base(mytype)
    { }
}

The lookup is causing performance issues? 查找导致性能问题?
Dictionary lookup is 0(1) and few milliseconds. 字典查找是0(1)和几毫秒。
A program can only have so many classes. 一个程序只能有这么多的类。
Entity still needs to create the object, create a new EntityData, and run Invoke. 实体仍然需要创建对象,创建新的EntityData并运行Invoke。
In addition to initialization of the classes that implement Entity. 除了初始化实现Entity的类之外。

Why does the type of the sub class effect the way an encapsulated class should be populated? 为什么子类的类型会影响填充封装类的方式? This seems like a violation of some OO principles to me. 这似乎违反了一些面向对象的原则。


If there is some specialized behaviour for a subclass, then 如果某个子类有一些特殊的行为,那么

public abstract class Entity
{
    private readonly EntityData data = InitializeData(new EntityData());

    protected abstract void InitializeData(EntityData data);
}

seems like a better definition for the base class. 对基类来说似乎是一个更好的定义。 The specilased action can be defined in the sub class, 可以在子类中定义specilased动作,

Public class Employee : Entity
{
     protected override void InitializeData(EntityData data)
     {
        // Employee specific implementation here ...
     }
}

This requires no Dictionary , lookup or even a switch statement. 这不需要Dictionary ,查找甚至switch语句。 No static state is required. 不需要静态。 It means the sub class related code has to be in the sub class but, that is a good thing, that is OO. 这意味着子类相关的代码必须在子类中,但这是一件好事,那就是OO。


If its necessary to preserve more of what you have your could do somthing like, 如果有必要保留你所拥有的更多你可以做的事情,比如

public abstract class Entity
{
    private readonly EntityData data;

    protected Entity(Action<EntityData> initializeData)
    {
        this.data = initializeData(new EntityData());
    }
}

public class Employee : Entity
{
    public Employee : base(SomeStaticAction)
    {
    }
}

I really feel like you're overthinking this. 我真的觉得你在想这个。 Why not just have Entity have an abstract get-only property that needs to be overridden? 为什么不让Entity有一个需要重写的抽象get-only属性?

public abstract class Entity
{
    private static Dictionary<Type, Action<EntityData>> _initActions = 
                new Dictionary<Type, Action<EntityData>>();

    protected abstract EntityData _data { get; }

    protected Entity()
    {
        _initActions[this.GetType()].Invoke(_data);
    }
}

public class Employee : Entity
{
    public string Name { get; set; }
    protected overrides EntityData _data { 
         get { return new EntityData("Employee Stuff"); } 
    }
}

public class Manager : Employee
{
    public List<Employee> Subordinates { get; set; }
    protected overrides EntityData _data { 
         get { return new EntityData("Manager Stuff"); } 
    }
}

Alternatively, just have two Init methods. 或者,只有两个Init方法。

public abstract class Entity
{
    private static Dictionary<Type, Action<EntityData>> _initActions = 
                new Dictionary<Type, Action<EntityData>>();

    private void InitalizeBase() { /* do shared construction */ }
    protected abstract void Initalize();

    protected Entity()
    {
        InitalizeBase();
        Initalize();
    }
}

public class Employee : Entity
{
    public string Name { get; set; }
    protected overrides Initalize()
    {
        // Do child stuff
    }
}

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

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