【问题标题】:Store same instance of object in Dictionary & ObservableCollection在 Dictionary & ObservableCollection 中存储相同的对象实例
【发布时间】:2021-08-22 11:26:11
【问题描述】:

我正在构建一个 WPF 应用程序。假设我们有一个超级市场;两种类型的对象称为CustomerProductCustomer 对象存储了他在购物车中的 Products。我有两个字典,用于存储 CustomerProduct 对象的实例(两者都是唯一的)。

测试数据

客户列表 (Dictionary<String,Customer>) |客户| | -------- | |客户 A | |客户 B |

产品列表 (Dictionary<String,Product>)
|产品| | -------- | |苹果 | |香蕉 | |西瓜| |黄瓜|


CustomerA
Apple
Banana
CustomerB
Apple
Watermelon

我有一个Customer 类型的ObservableCollection CustomerList,我显示并使用它来修改我的Customers(和他们的Products)。我想实现以下内容:当我从ProductList 中删除产品Apple 时,我希望它从CustomerACustomerB 的“购物车”中消失。我在想的是每个Customer 应该将产品存储为指向ProductList 所需索引的指针(我已经做了一些C++)。但是,当我在线阅读时,似乎指针在 C# 中是一个很大的禁忌。有没有容易做到这一点?当我从 ProductList 删除或修改产品使其“无法购买”时,我不想管理每个 Customer

编辑:添加了要求,我还希望能够修改 Productlist 中的 Apple 产品(例如将其重命名为 Red Apples)并让客户 A 和客户 B“购物车”也相应更改。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace ConsoleApp1
{
public class Customer : INotifyPropertyChanged
{
    private string name;

    public ObservableCollection<Product> Items { get; set; }
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    public Customer(string _name)
    {
        name = _name;
        Items = new ObservableCollection<Product>();
    }

    public void AddItem(Product item)
    {
        Items.Add(item);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyRaised(string propertyname)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
        }
    }
}

public class Product : INotifyPropertyChanged
{
    private string name;
    private Guid guid;
    private double price;

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    public Guid GUID
    {
        get
        {
            return guid;
        }
        set
        {
            guid = value;
        }
    }

    public double Price
    {
        get
        {
            return price;
        }
        set
        {
            price = value;
        }
    }

    public Product(string _name, double _price)
    {
        name = _name;
        price = _price;
        guid = Guid.NewGuid();
    }

    public void Modify(string _newname,double _price)
    {
        name = _newname;
        price = _price;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyRaised(string propertyname)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
        }
    }

}

public class MarketServices
{
    // This will hold all my unique instances of Customer objects
    public Dictionary<String, Customer> CustomerObjectsMainList_Services = new Dictionary<String, Customer>();

    // This will hold all my unique instances of Product objects
    public Dictionary<String, Product> ProductObjectsMainList_Services = new Dictionary<String, Product>();

    // This will be used to display customers in a listview/datagrid and do modifications on it
    // I want the objects in this observable collection to be "linked" or "point" to the instances in CustomerObjectsMainList_Services
    // Such that if I modify any customername for example it would be reflected here too
    // Also changing any product name or removing any product from ProductObjectsMainList_Services should be reflect in the "cart" of all customers
    // in CustomerObjectsMainList_Services and CustomerObjectsList_Services
    public ObservableCollection<Customer> CustomerObjectsList_Services = new ObservableCollection<Customer>();

    public MarketServices()
    {
        this.CreateCustomers();

        //Here I want to fill CustomerObjectsList_Services with the instances of customers in CustomerObjectsMainList_Services
        foreach (var kvp in CustomerObjectsMainList_Services)
            CustomerObjectsList_Services.Add(kvp.Value);

        //Now if I delete a product from the ProductObjectsMainList_Services, will it get reflected on the Customer objects?
    }

    public void CreateCustomers()
    {
        CustomerObjectsMainList_Services.Add("CustomerA", new Customer("CustomerA"));
        CustomerObjectsMainList_Services.Add("CustomerB", new Customer("CustomerB"));

        ProductObjectsMainList_Services.Add("Apple", new Product("Apple",10));
        ProductObjectsMainList_Services.Add("Banana", new Product("Banana",15));
        ProductObjectsMainList_Services.Add("Watermelon", new Product("Watermelon",20));
        ProductObjectsMainList_Services.Add("Cucumber", new Product("Cucumber",25));

        CustomerObjectsMainList_Services["CustomerA"].AddItem(ProductObjectsMainList_Services["Apple"]);
        CustomerObjectsMainList_Services["CustomerA"].AddItem(ProductObjectsMainList_Services["Banana"]);

        CustomerObjectsMainList_Services["CustomerB"].AddItem(ProductObjectsMainList_Services["Apple"]);
        CustomerObjectsMainList_Services["CustomerB"].AddItem(ProductObjectsMainList_Services["Watermelon"]);
    }
}


class Program
{
    public static void PrintCustomer(Customer customer)
    {
        foreach (var x in customer.Items)
        {
            Console.WriteLine("{0}          {1}            {2}", x.Name, x.Price,x.GUID);
        }
    }

    static void Main(string[] args)
    {
        MarketServices test = new MarketServices();

        test.CustomerObjectsList_Services.Add(test.CustomerObjectsMainList_Services["CustomerA"]);
        test.CustomerObjectsList_Services.Add(test.CustomerObjectsMainList_Services["CustomerB"]);

        Console.WriteLine("Printing Customer A objects");
        PrintCustomer(test.CustomerObjectsMainList_Services["CustomerA"]);

        Console.WriteLine();
        Console.WriteLine();

        Console.WriteLine("Printing Customer B objects");
        PrintCustomer(test.CustomerObjectsMainList_Services["CustomerA"]);

        Console.WriteLine();
        Console.WriteLine();

        test.ProductObjectsMainList_Services["Apple"].Modify("Peaches",5);

        Console.WriteLine("Printing Customer A objects after changing Apples to Peaches");
        PrintCustomer(test.CustomerObjectsList_Services[0]);

        test.ProductObjectsMainList_Services["Peaches"] = null;
        Console.WriteLine("Printing Customer A objects after removing Peaches");

        PrintCustomer(test.CustomerObjectsList_Services[0]);

    }
}

}

Edit2:提供修改后的代码;如果我修改任何产品名称或价格,上述方法有效。但是,我不确定如何从 ProductObjectsMainList_Services 中“删除”产品并将其从所有在“购物车”中拥有该产品的客户中删除。

【问题讨论】:

  • 嗯,这样做的一种方法是让产品成为带有布尔标志(例如“可用”)的单例(一个实例)。当您将其设置为 true 时,这不会删除该实例,但您可以在检查 bool 时从任何集合中忽略(或删除)该产品。
  • @zer0 将使其成为单例意味着我只能拥有一个产品对象(仅限 Apple?)
  • Singleton 表示特定类的一个实例。所以你可以创建一个 Apple 和 Banana 类。如果产品具有共享属性,请使用IProduct 之类的接口。例如,您的字典将更改为Dictionary&lt;String,IProduct&gt;。如果所有产品共享一些代码,您也可以在此处使用抽象基类。
  • @zer0 那是行不通的,因为我的产品列表可以动态更改。我可能会在运行时添加产品。我不能用 C++ 中的指针来做到这一点吗?
  • 你可以很容易地在运行时创建单例(我会使用泛型,但不是必需的)。想要一个例子吗?是的,C# 可以使用指针,但现在您使用的是 unsafe 代码。

标签: c#


【解决方案1】:
public class Product
{
    public string Name { get; init; }
    public double Price { get; init; }
    public bool Available { get; set; } = true;
}

//thread safe collection of single instances of products (by name)
//gurantees one instance of product class for a given name
public static class ProductList
{
    private static readonly ConcurrentDictionary<string, Product> allProducts 
    = new ConcurrentDictionary<string, Product>();

    //creates product, or returns existing one if same one with name already exists
    public static Product AddProduct(string name, double price)
    {
        return allProducts.GetOrAdd(name, new Product { Name = name, Price = price });
    }

    //Gives you the only instance of that product
    public static Product GetProduct(string name) => allProducts[name];
}

用法很简单。致电AddProduct 创建新产品。线程安全,永远不会在所有产品的字典中存储第二个实例。

然后使用GetProduct 获取该产品名称的唯一实例。

如果您不需要线程安全,您可以使用Dictionary 而不是ConcurrentDictionary

您的编辑使这个答案没有意义(我可以删除)。这假定产品是基于名称的单例。

如果您想更改价格或除唯一键(名称)之外的任何其他内容,您只需修改类实例即可。

或更改AddProduct 以使用ConcurrentDictionary.AddOrUdpate 保留相同的实例但修改它。

也就是说,如果您不能唯一地键入某些内容(对于并发字典),这种设计就会失效。

例如,如果存在可用于字典键而不是名称的唯一标识符(如产品 ID 等),则可以重命名产品。

【讨论】:

  • 谢谢。这说明了单例,但我如何将其应用于上述案例?您提到将新产品添加到产品列表中;但以上如何解决我的问题? (从产品列表中删除或修改苹果产品将从所有客户中删除或修改)
  • 非常感谢您的努力。我修改了我的帖子以添加另一个问题(修改或重命名产品)。现在要睡觉了,早上会试着理解和测试你的方法。再次感谢。
  • @MElSawy 已更新,但如果对象没有唯一的不可变键,则此方法没有实际意义。如果产品有一些不可变的密钥,那么我可以回答您有关删除之类的问题。我想对于名称更改,您可以使用相同的实例,并更改字典键(删除旧键,添加新键,使用相同的实例)。但是通过改变键来获取产品实例会变得很奇怪......
  • 你能看看我提供的代码吗? @Zer0。它适用于修改名称或产品价格。如何实现“删除”?另外我认为您的代码缺少一些东西(GetOrAdd 方法?)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-18
  • 2019-11-03
  • 2014-09-19
  • 2021-10-22
  • 2011-02-27
  • 1970-01-01
  • 2012-05-05
相关资源
最近更新 更多