Home > Net >  How can I force an instance created with Activator to use the method from the base class?
How can I force an instance created with Activator to use the method from the base class?

Time:10-08

I have a class that is derived from the observable Collection class:

class MyClassItem
{
    ...
}

class CustomCollection : ObservableCollection<MyClassItem>
{
    public CustomCollection(){}
    public new void Add(MyClassItem item)
    {
       //...Some custom stuff that I do
       base.Add(item);
    }
}

I have another class which is supposed to be a generic checklist implementation that can support any type and allow the caller get a collection of their desired type. My implementation looks like this:

class GenericCheckListItem<T>
{
   //...My implementation stuff
   public T Item {get; set;}
}

class MyGenericCheckList<T> : ObservableCollection<GenericCheckListItem<T>>
{
    //...Some implementation stuff
    
    public CT GetSelectedItemsAsCollection<CT>() where CT: ICollection<T>
    {
        CT collection = Activator.CreateInstance<CT>();
        foreach(var item in this)
        {
            collection.Add(item.Item)
        }
        return collection;
    }
}

The above is to be used like so:

MyGenericCheckList<MyClassItem> cList = new MyGenericCheckList<MyClassItem>();
// ...add some stuff and edit the selection of cList
var myColl = cList.GetSelectedItemsAsCollection<CustomCollection>();

My issue is that I keep noticing that although the collection instance that is created in the GetSelectedItemsAsCollection method is of type CT, the Add method that gets called is not my shadowed implementation in CustomCollection, but some other (I assume the base implementation). I can tell because there is some important book keeping done whenever an item is Added that is not being executed.

Now my question is how do I force my method to call the shadowed implementation of the Add method. If this is not possible is there some other approach I can use to achieve my desired results?

CodePudding user response:

Basically your code works as you expect it. I've tried it like this

public class Person
{
   public string FirstNames { get; set; }
   public string LastName { get; set; }
}

public class PersonsCollection : ObservableCollection<Person>
{
   public new void Add(Person person)
   {
      Console.WriteLine($"PersonsCollection.Add: {person.FirstNames} {person.LastName}");
      Console.WriteLine($"no of items before add: {this.Count}");
      base.Add(person);
      Console.WriteLine($"no of items after add: {this.Count}");
   }
}

public class CollectionFactory<T>
{
   public CT CreateCollection<CT>() where CT: ICollection<T>
      => Activator.CreateInstance<CT>();
}

If you use this code like this,

var factory = new CollectionFactory<Person>();
var persons = factory.CreateCollection<PersonsCollection>();
persons.Add(new Person { FirstNames = "John", LastName = "Doe" });

you get the output

PersonsCollection.Add: John Doe
no of items before add: 0
no of items after add: 1

So, if your Add method is not called, it must have a reason outside ouf the presented code (my omitted constructor has no influence).

As you are using ObservableCollection wouldn't it be a more straightforward way to subscribe to the CollectionChanged event to do some stuff? I see, that you put your stuff before the call to base.Add(...), but that should solvable.

CodePudding user response:

The direct/oversimplified answer to your question is to make your CustomCollection class implement ICollection<MyClassItem>:

class CustomCollection : ObservableCollection<MyClassItem>, ICollection<MyClassItem>

This makes your collection, when cast as an ICollection<MyClassItem>, recognize your new Add method as the one which implements that interface. However, it will not have the same behavior if you cast an item as ObservableCollection<MyClassItem>.

The longer, probably more correct answer is that it's generally a bad idea to extend basic collection types like this. Most likely whatever you're trying to accomplish with this can probably be accomplished in other ways. For example, you should be able to do your "important book keeping" by subscribing to the CollectionChanged handler on a plain old ObservableCollection. If you're adding other features to your custom collection class, consider using a container class instead, with the ObservableCollection as a separate property on that class. In other words, using composition instead of inheritance.

  • Related