【问题标题】:Get Previously Instantiated Object from Heap for Variable Reference从堆中获取先前实例化的对象以进行变量引用
【发布时间】:2018-02-03 22:11:15
【问题描述】:

我有 2 个类包含另一个的类属性。

public class Client
{
    public Customer Customer { get; set; }

    public Client()
    {

    }
}

public class Customer
{
    public Client Client { get; set; }

    public Customer(int id)
    {
        // some retrieval logic by using id ...
    }
}

这是一个工厂类,它显示了如果我在编译时间之前就知道对象,我正在尝试实现的概念。实际上,我不需要也必须通过用户提供的数据的反射来实例化它们。现在考虑到这一点,我希望将相反类的对象属性分配给先前为该其他类创建的对象(即如下所示)

public class Factory
{
    public Factory()
    {
        // this is all done via reflection at run-time in my real code
        Client client = new Client();
        client.Customer = new Customer(1); // id would be retrieve from a client property (not shown for simplicity sake)
        client.Customer.Client = client; // refer to the first object of client
    }
}

但是,利用之前提供的工厂类示例,我的代码中的客户端对象不在范围内,或者在堆栈上更高(各种属性是递归实例化的,如果更高的类具有相同类型的属性)跳过它的子后续类/对象(以避免无限递归)。

如何在工厂方法中利用类名或任何对象的属性将属性指向先前实例化的对象,而该对象可能不在范围内?

我正在考虑使用列表/字典来存储更高级别的对象引用并通过递归将它们向下传递并检查对象的类型是否与子后续属性的类型匹配,然后仅使用存储在该属性的列表/字典。我想看看这是最好的方法还是有其他方法。

这纯粹是理论上的,可能会也可能不会被使用。目标是从这些类创建任何时间和对象,所有相关类在第一个对象中都有属性,其中包含相关类的实例化实例,这些实例永远不会为空/不会无限回避。

【问题讨论】:

  • 答案就在问题中。
  • @Guillaume 谢谢,我知道,但我只是想知道这是最好的方法还是有更好的方法。

标签: c# asp.net oop object recursion


【解决方案1】:

我建议您对CustomerClient 都使用工厂,并进行一点小改进:您将需要使用实现缓存的工厂。一个例子可以在in this answer找到。

当您构造Customer 时,只需从缓存工厂填充Client。反之亦然。缓存机制将负责其余的工作。当然,您会希望将工厂作为单例实例注入,以便缓存在所有使用工厂的代码中都是通用的。

客户工厂可能看起来像这样:

public class CustomerFactory : ICustomerFactory
{
    private readonly IClientFactory clientFactory;  //To be injected

    private readonly ConcurrentDictionary<int, ICustomer> customers = 
                 new ConcurrentDictionary<int, ICustomer>();

    public CustomerFactory(IClientFactory clientFactory)
    {
        this.clientFactory = clientFactory; //Injected
    }

    public ICustomer GetCustomer(int id)
    {
        ICustomer customer = this.customers.GetOrAdd(id, () => new Customer(id));
        if (customer.Client == null)
        {
            customer.Client = this.clientFactory.GetClient(customer.ClientID);
        }
        return customer;
    }
}

在上面的示例中,您会注意到在尝试设置 Client 属性之前,新实例化的 Customer 被添加到缓存中。这对于避免无限循环非常重要。如果您尝试在 Customer 进入缓存之前检索 Client,ClientFactory 将无法找到它,并且最终可能会创建一个新实例。

另一方面,也许您不需要立即设置 Client 属性。毕竟,你现在有了一个缓存机制,所以你可以懒惰地设置它。我们可以从GetCustomer中删除代码...

    public ICustomer GetCustomer(int id)
    {
        return this.customers.GetOrAdd(id, () => new Customer(id));
    }

...仅在需要时检索客户端。

class Customer
{
   public Client Client
   {
       get
       {
           return this.clientFactory.GetClient(this.ClientID);
       }
   }
}

虽然一遍又一遍地调用 ClientFactory 似乎很昂贵,但所发生的一切都是在字典中快速查找。如果调用者甚至不需要客户端,我们将节省到数据库的往返行程。

这整个想法与您在问题结束时提出的想法没有太大区别,只是您不需要传递任何参考资料。唯一需要的引用是对工厂的引用。

【讨论】:

  • 我喜欢你的想法,但是(这是因为我在最初的问题中没有引用这个),我设置工厂的方式是通过通用工厂(即 Factory)和该工厂的实现初始化单个类+子类。泛型工厂“反映”属性并检查特定接口(类所基于的接口),然后动态创建泛型工厂的实例,将反射属性的类型作为泛型类型传递。因此,所有工厂的实例化都是通过动态从泛型创建工厂来动态完成的。
  • 你让我很好奇。您如何反映属性的类型,然后将其作为泛型类型传递?菱形运算符不接受运行时变量,即。你不能去var o = new GenericType&lt;someVariable&gt;
  • .NET 反射框架是关键。您从要实例化的泛型类的类型中使用 MakeGenericType 方法,然后为该方法使用 GetMethod,然后是 Activator.CreateInstance(传递 MakeGenericType 类型结果,您的构造函数参数 [如果有])。它对我来说效果很好,但是激活器有优化,但我没有彻底研究它们。它不是在编译时完成的,但我不介意通过在单个方法/类中牺牲少量编译时间(AKA Intellisense)知识来换取更清洁、更精简和更健壮的代码
猜你喜欢
  • 2014-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-08
  • 1970-01-01
相关资源
最近更新 更多