0

I have read lots of questions and answers to solve this problem, but haven't found anything that helps with this problem. I am trying to expose a minimal example, so this is not exactly my real code (if there is any mistake in it, please, advice me).

What do I want to do? I am just trying to create a UserControl that uses a generic class. I want to use it like the WPF frameworks works, I mean, I want to be able to bind a property to a generic object of any class (Itemssource in the WPF framework case, you can bind it to ObservableCollection<AnyClass>).

First of all I define my class:

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

Then I create a UserControl (MyUserControl) with this DependencyProperty in it:

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 ...
}

This code compiles, but it does not work. If I set a breakpoint in OnItemsListChanged, it never fires.

I can change typeof(MyGenericClass<IMyItemInterface>) to typeof(object) this way:

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

Now it does fire OnItemsListChanged but now I have a casting excepcion in get => (MyGenericClass<IMyItemInterface>)GetValue(ItemsListProperty);, and it is logical because MyGenericClass derives from object and not object from MyGenericClass. So I have also tried to change the property to:

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

Now it compiles again and remains firing OnItemsListChanged but now I cannot use ItemList for anything because I cannot refer to ItemLists.MySelectedItem (for example), because MySelectedItem does not exist in object class.

The question is: what is the right approach for it? what am I doing wrong?

UPDATE:

What I bind to ItemsList is an instance of MyGenericClass. For example, I have a class that implements IMyItemInterface (MyClassItem), and after doing in code-behind MyGenericClass<MyClassItem> myItem = new();, I bind in XAML this way:

<MyCustomControl ItemsList={Binding myItem, Mode=TwoWay} />
Carlos
  • 1,638
  • 5
  • 21
  • 39
  • Is MyGenericClass supposed to be an IEnumerable or ICollection or IList? Than use IEnumerable/ICollection/IList as the type of the dependency property. Also clarify why your UserControl should implement INotifyPropertyChanged. That's pointless for dependency properties. – Clemens Jan 07 '22 at 11:41
  • @Clemens you are right, INotifyPropertyChanged is pointless for dependency properties, I edit not the question and remove it. About the other question, no, it is not an IEnumerable, but it has properties inside which are IEnumerable. I update the question. Thank you – Carlos Jan 07 '22 at 12:13
  • Any binding errors in the Output window? – Jeroen van Langen Jan 07 '22 at 12:17
  • @Carlos: What do you set or bind your `ItemsList` property to? – mm8 Jan 07 '22 at 12:27
  • @mm8 I have updated the question with this information. Thank you – Carlos Jan 07 '22 at 12:49
  • Why is there `Mode=TwoWay` on the ItemsList Binding? Does MyCustomControl set the value of its ItemsList property - which is something different than modifying the properties of the MyGenericClass instance? – Clemens Jan 07 '22 at 12:58
  • @JeroenvanLangen Yes: System.Windows.Data Error: 5 : Value produced by BindingExpression is not valid for target property. MyGenericClass`1:'MyGenericClass`1[MyClassItem]' BindingExpression:Path=myItem; DataItem='MyViewModel' (HashCode=30427456); target element is 'MyCustomControl' (Name=''); target property is 'ItemsList' (type 'MyGenericClass`1') MyGenericClass – Carlos Jan 07 '22 at 12:59
  • @Clemens I set Mode=TwoWay because I would like to update some properties of `MyGenericClass` from the UserControl. But maybe it is not important for my problem, maybe you can ignore Mode=TwoWay, because my real problem is the binding and the cast of the interface. Thank you – Carlos Jan 07 '22 at 13:01

2 Answers2

2

You can't set a property of type MyGenericClass<IMyItemInterface> to an MyGenericClass<MyClassItem> because a MyGenericClass<MyClassItem> is not a MyGenericClass<IMyItemInterface> just because MyClassItem implements IMyItemInterface.

This is called variance and C# restricts variant type parameters to generic interfaces and generic delegate types.

For example, you can set an IEnumerable<object> field to a List<string> but you cannot set a List<object> field to a List<string> despite the fact that List<T> implements IEnumerable<T>.

That's your issue and why your dependency property isn't set and your callback isn't invoked.

Please refer to this question and answers for more information:

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

The docs on covariance and contravariance should also be helpful.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • After reading your answer, I have come to the conclusion I have to study more (a lot more)... Thank you. But now I have one (maybe stupid) question... would it help if `MyGenericClass` implemented IEnumerable interface? As it is a covariant interface, would it help with the casting? Thank you – Carlos Jan 08 '22 at 10:15
1

It may be sufficient to declare the dependency property type like this:

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

and the dependency property like this:

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));

Assignments like these (and equivalent Bindings) would now work:

c.ItemsList = new MyItemsList { MySelectedItem = new MyClassItem() };
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Yes, this would be one possible solution, but this is not a generic class. I need it to be generic, `MyGenericCollection` (or `MyItemsList` as you named it), with generics is where my problem appears. I would like to be able to bind an object of `MyGenericCollection` or and object of `MyGenericCollection` to MyUserControl, the same way I bind `MyGenericCollection` and `MyGenericCollection` to ItemsSource of an ObservableCollection without any problem. Thank you – Carlos Jan 08 '22 at 10:14
  • 1
    But you should have understood that that won't work. "*I need it to be generic*" is only true as long as you don't think about alternatives. – Clemens Jan 08 '22 at 10:44
  • Sure, but I am still missing something. Sorry. – Carlos Jan 08 '22 at 10:49
  • 1
    You are absolutetly right. I have finally understood it. Thank you – Carlos Jan 08 '22 at 11:23
  • Maybe this link is useful for someone with the same problem: https://levelup.gitconnected.com/a-best-practice-for-designing-interfaces-in-net-c-2c6ebdb4f1c1 – Carlos Jan 14 '22 at 08:07