UserControl 中泛型 class 的 C# DependencyProperty

C# DependencyProperty of generic class in UserControl

我已经阅读了很多问题和答案来解决这个问题,但没有找到任何有助于解决这个问题的方法。我试图公开一个最小的例子,所以这不是我的真实代码(如果有任何错误,请告诉我)。

我想做什么?我只是想创建一个使用通用 class 的 UserControl。我想像 WPF 框架一样使用它,我的意思是,我希望能够将 属性 绑定到任何 class 的通用对象(在 WPF 框架情况下为 Itemssource,你可以将它绑定到 ObservableCollection<AnyClass>).

首先我定义我的 class:

public class MyGenericClass<T> where T : IMyItemInterface
{ 
    public ObservableCollection<T> MyOriginalList { get; set; }
    public T MySelectedItem { get; set; }
    ... more code here ...
}

然后我创建一个 UserControl (MyUserControl),其中包含此 DependencyProperty:

public partial class MyUserControl: UserControl
{
... code here ...

public static readonly DependencyProperty ItemsListProperty =
    DependencyProperty.Register("ItemsList", typeof(MyGenericClass<IMyItemInterface>),
    typeof(MyUserControl),
    new FrameworkPropertyMetadata(new PropertyChangedCallback(OnItemsListChanged)));

private static void OnItemsListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    MyUserControl myControl = (MyUserControl)d; // just for debugging
}

public MyGenericClass<IMyItemInterface> ItemsList
{
    get => (MyGenericClass<IMyItemInterface>)GetValue(ItemsListProperty); 
    set => SetValue(ItemsListProperty, value);
}

... more code here ...
}

此代码可以编译,但无法运行。如果我在 OnItemsListChanged 中设置断点,它永远不会触发。

我可以这样把 typeof(MyGenericClass<IMyItemInterface>) 改成 typeof(object):

public static readonly DependencyProperty ItemsListProperty =
    DependencyProperty.Register("ItemsList", typeof(object),
    typeof(MyUserControl),
    new FrameworkPropertyMetadata(new PropertyChangedCallback(OnListaItemsChanged)));

现在它确实触发了 OnItemsListChanged 但现在我在 get => (MyGenericClass<IMyItemInterface>)GetValue(ItemsListProperty); 中有一个转换异常,这是合乎逻辑的,因为 MyGenericClass 派生自对象而不是派生自 MyGenericClass 的对象。所以我也尝试将 属性 更改为:

public object ItemsList
{
    get => GetValue(ItemsListProperty); 
    set => SetValue(ItemsListProperty, value);
}

现在它再次编译并继续触发 OnItemsListChanged 但现在我无法将 ItemList 用于任何事情,因为我无法引用 ItemLists.MySelectedItem(例如),因为 MySelectedItem 不存在于object class.

问题是:正确的方法是什么?我做错了什么?

更新:

我绑定到 ItemsList 的是 MyGenericClass 的一个实例。例如,我有一个实现 IMyItemInterface (MyClassItem) 的 class,在代码隐藏 MyGenericClass<MyClassItem> myItem = new(); 中完成后,我以这种方式绑定 XAML:

<MyCustomControl ItemsList={Binding myItem, Mode=TwoWay} />

您不能将 MyGenericClass<IMyItemInterface> 类型的 属性 设置为 MyGenericClass<MyClassItem>,因为 MyGenericClass<MyClassItem> 而不是 MyGenericClass<IMyItemInterface> 只是因为 MyClassItem 实现了 IMyItemInterface.

这称为变体,C# 将变体类型参数限制为泛型接口和泛型委托类型。

例如,您可以将 IEnumerable<object> 字段设置为 List<string>,但不能将 List<object> 字段设置为 List<string>,尽管 [=20] =] 实现 IEnumerable<T>.

这是您的问题,也是您未设置依赖项 属性 且未调用回调的原因。

请参阅此问题和答案以获取更多信息:

C# variance problem: Assigning List<Derived> as List<Base>

docs on covariance and contravariance 应该也有帮助。

像这样声明依赖项 属性 类型可能就足够了:

public class MyItemsList
{
    public ObservableCollection<IMyItemInterface> MyOriginalList { get; set; }
    public IMyItemInterface MySelectedItem { get; set; }
}

和依赖项 属性 像这样:

public MyItemsList ItemsList
{
    get { return (MyItemsList)GetValue(ItemsListProperty); }
    set { SetValue(ItemsListProperty, value); }
}

public static readonly DependencyProperty ItemsListProperty =
    DependencyProperty.Register(
        nameof(ItemsList), typeof(MyItemsList), typeof(MyUserControl));

像这样的赋值(和等效的绑定)现在可以工作了:

c.ItemsList = new MyItemsList { MySelectedItem = new MyClassItem() };