避免在MVVM模式中使用ObservableCollection

3

我有一个包含集合的Model类和一个ViewModel封装类,该封装类实现了INotifyPropertyChanged接口,并为模型类中的每个属性提供了一个封装属性。我这样实现是为了使模型类尽可能独立于任何WPF命名空间,因为这些类也用于另一个项目(Windows服务)。简化后的实现如下:

Model

public class FbiDirectory
{
    private string type;
    private ObservableCollection<PluginValue> pluginValues = new ObservableCollection<PluginValue>();

    public string Type
    {
        get
        {
            return this.type;
        }

        set
        {
            this.type = value;
        }
    }

    public ObservableCollection<PluginValue> PluginValues
    {
        get
        {
            return this.pluginValues;
        }

        set
        {
            this.pluginValues = value;
        }
    }
}

ViewModel 包装器

public class FbiDirectoryViewModel : INotifyPropertyChanged
{
    private FbiDirectory fbiDirectory = new FbiDirectory();

    public string Type
    {
        get
        {
            return this.fbiDirectory.Type;
        }

        set
        {
            this.fbiDirectory.Type = value;

            this.OnPropertyChanged("Type");
            this.OnPropertyChanged("Title");
        }
    }

    public ObservableCollection<PluginValue> PluginValues
    {
        get
        {
            return this.fbiDirectory.PluginValues;
        }

        set
        {
            this.fbiDirectory.PluginValues = value;

            this.OnPropertyChanged("PluginValues");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

我的问题是是否有一种方法可以将模型中的 PluginValues 集合类型更改为 List ,并且仍然具有ViewModel中 ObservableCollection 的功能。也许使用某种转换器或强制转换之类的方式可以实现这一点。

你为什么想要这样做? - Ahmad ElMadi
@BraveHeart 我需要在另一个项目中使用模型类。WPF应用程序用于设置模型类中所需的所有值,然后将这些值序列化为XML文件。然后,Windows服务对该文件进行反序列化,并使用这些设置来执行一些任务。但是我不想在Windows服务项目中导入一些特定于WPF的引用,只是为了使用可观察集合。 - Romano Zumbé
1
只要为属性引发适当的通知事件,就不需要使用ObservableCollection进行绑定。List<>或数组同样可以胜任。ObservableCollection在每次修改集合时都会引发事件。如果需要,您也可以在代码中执行相同的操作。 - Panagiotis Kanavos
1
ObservableCollection位于命名空间System.Collections.ObjectModel和程序集System.dll中。它不是WPF,而是核心.NET Framework。 - Clemens
@PanagiotisKanavos 当然,它们有一个不同的INotifyCollectionChanged实现。你甚至可以编写自己的实现,但是为什么呢? - Clemens
显示剩余13条评论
3个回答

6
您可以将模型类保持为常规的List<>,并且在View Model上具有匹配的属性是ObservableCollection<>。我经常这样做而没有任何问题。在View Model的构造函数中(接受Model作为参数),我只需从Model的属性内容实例化View Model的ObservableCollection<>即可。如果您实际上也要包装属性Type,则此方法特别有效。
当Model属性更改时,您不会自动更新View Model属性,因此您需要自己保持同步,但我认为这是View Model的工作的一部分,尤其是当您包装一个您无法控制的Model时。
public void MyViewModel(MyModel<MyModelPropertyType> model)
{
    MyListProperty = new ObservableCollection<MyWrappedModelPropertyType>(model.ModelListProperty.Select(i => new MyWrappedModelPropertyType(i));
}

如果您不需要包装属性类型,可以省略 LINQ。这样做会导致一些开销,比如实例化时间和内存成本等方面的复制列表。如果需要包装属性类型,则无论如何都需要这样做。

1
我的问题是是否有一种方法可以使Model中的PluginValues集合类型为List,并仍然具有ViewModel中ObservableCollection的特性。
不行。ObservableCollection实现了INotifyCollectionChanged接口,而List没有。
因此,如果您打算在运行时动态添加项目到PluginValues集合中,则应该使用ObservableCollection或任何其他实现INotifyCollectionChanged接口的集合类型。
但是,正如评论中指出的那样,ObservableCollection并不是一个真正的WPF特定类,因为它在System.dll中的System.Collections.ObjectModel命名空间中定义。因此,您可以在模型中直接使用ObservableCollection,也可以实现自己的自定义集合来引发通知。后者可能看起来有点必要。

MVVM框架即使没有ObservableCollection也可以正常工作。你不需要ObservableCollection来获取INotifyCollectionChanged通知 - 实际上,在最常见的情况下,当属性被设置时,你无法从ObservableCollection中获得这样的通知。ObservableCollection只是一个辅助工具。 - Panagiotis Kanavos
关于动态修改集合,那应该通过命令或 VM 方法完成,可以(并且应该)引发所有适当的通知。通常最好在最后发送所有通知,而不是为每个修改的单个项目引发一个通知,强制重绘。 - Panagiotis Kanavos
如果您正在修改视图模型中的集合,那么最好直接向包装的ObservableCollection添加项。这样就不会有问题了。但在这种情况下,我猜测模型集合是在模型中动态更新的。 - mm8

-1

你的ModelViewModel之间没有任何继承或其他"强制"关系,所以你有几个选项:

选项1

如果没有继承关系,你可以在你的模型中拥有同名但类型不同的属性。只需在这两个类之间进行映射时要小心。

选项2

如果属性确实需要具有相同的类型,只需使用IList<>IEnumerable<>即可。

IList<object> list1 = new ObservableCollection<object>();
IList<object> list2 = new List<object>();

C#中的所有集合都实现了IList,因此所有集合都可以以IList类型存储在变量或属性中。 如果您不需要任何特殊的列表功能,如AddClearContains等,甚至可以使用IEnumerable<>作为属性类型。

选项3

如果ModelViewModel之间存在继承关系,则可以使用new关键字重写属性。

public class Model 
{
    public List<object> Plugins { get; set; }
}

public class ViewModel : Model 
{
    public new ObservableCollection<object> Plugins { get; set; }
}

上面的代码是有效的,并且会更改 `Plugin` 的类型。只要注意使用 `new` 关键字可能会产生一些奇怪的影响。
假设您使用的是 WPF(猜测是因为 MVVM 和 C#),第三个选项将不起作用。WPF 使用反射进行数据绑定。如果您使用 `new` 覆盖属性,则 WPF 将找到两个实现,由于不知道使用哪一个,它会引发异常。
结论:
我个人更喜欢选项1。模型、DTO和ViewModels没有必要继承相同的基类,也没有理由它们不能具有相同名称但不同类型的属性。
如果您想让ViewModels从Models继承,请使用选项二。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接