trackerdog icon indicating copy to clipboard operation
trackerdog copied to clipboard

How to support my custom Collection type?

Open shaneh20 opened this issue 7 years ago • 10 comments

Hi, I've just started using TrackerDog (nice work btw!) and having a bit of trouble getting it to support my custom ObservableCollectionEx class that my model classes use. Here's some demo code:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using TrackerDog;

class Program
    {
        static void Main(string[] args)
        {
            //Initialise
            var config = ObjectChangeTracking.CreateConfiguration().TrackThisTypeRecursive(typeof(Car));
            //config.Collections.AddOrUpdateImplementation<IList<object>, ObservableCollectionEx<object>, DefaultCollectionChangeInterceptor<object>>();
            var tracker = config.CreateTrackableObjectFactory();

            //Create a Car
            Car car = new Car();
            car.Passengers = new ObservableCollectionEx<Passenger>();
            car.Passengers.Add(new Passenger { Name = "Frank" });

            //Start tracking
            car = tracker.CreateFrom(car);
        }
    }

    public class ObservableCollectionEx<T> : ObservableCollection<T>
    {
        public ObservableCollectionEx() : base()
        {
        }

        public ObservableCollectionEx(List<T> list) : base(list)
        {
        }
        
        public ObservableCollectionEx(IEnumerable<T> collection) : base(collection)
        {
        }
    }

    public class Car 
    {
        public virtual ObservableCollectionEx<Passenger> Passengers { get; set; }
    }

    public class Passenger 
    {
        public virtual string Name { get; set; }
    }
}

When the car = tracker.CreateFrom(car) line runs, it throws the exception:

Object of type 'Castle.Proxies.IList1Proxy' cannot be converted to type 'ConsoleApp37.ObservableCollectionEx1[ConsoleApp37.Passenger]

Looking at your code, it seems TrackerDog is creating a proxy IList collection which it tries to assign to Car.Passengers and fails since their types differ. I tried using AddOrUpdateImplementation to map IList to my ObservableCollectionEx class, but it produces the same error.

I noticed in your documentation, you advised us to use collection interfaces (IList, ICollection etc) in our models rather than concrete collection types. Unfortunately the model classes in my actual project are all dependent on my ObservableCollectionEx class. So, I can't easily change this.

Is there a way to make TrackerDog work with my custom collection type?

Thanks

shaneh20 avatar Aug 31 '18 03:08 shaneh20

Hey! Thank you for using TrackerDog.

I'll read your message today carefully and I'll add an answer.

mfidemraizer avatar Aug 31 '18 15:08 mfidemraizer

@shaneh20 Actually, if I'm not mistaken, why you would need to use ObservableCollection? An IList<T> proxy already implements INotifyPropertyChanged and INotifyCollectionChanged, so if you're trying to use two-way binding, the proxy should be enough.

In the other hand, currently TrackerDog won't support concrete classes: you need to use interfaces, because what you get is a proxy, not your desired implementation.

Maybe TrackerDog could be configured to wrap trackable collections with anything that implements INotifyPropertyChanged and INotifyCollectionChanged instead of creating run-time proxies, but currently I don't know what could be the impact of implementing this.

Is it possible that you use the proxies instead of ObservableCollection?

Another approach is that you track the whole changes and you implement an extension method .ToObservable() so you may do a.Passengers.ToObservable().

Please let me know your thoughts.

mfidemraizer avatar Sep 03 '18 11:09 mfidemraizer

@shaneh20 let me know if @mfidemraizer's answer helps

toumir avatar Sep 06 '18 16:09 toumir

My custom ObservableCollectionEx class (inherits from ObservableCollection) contains custom behaviour that various parts of my applications depend on (it contains various extra events that the base class does not provide). So, although its a great idea to make the IList proxy implement the INotifyPropertyChanged interfaces, I also need the extra custom logic in my custom ObservableCollectionEx too.

I like your idea of TrackerDog being able to wrap any existing collections instead of creating proxies as that would be ideal for me, although I understand it may require a lot of work on your part to rework TrackerDog to support it.

If I created an IObservableCollectionEx interface and changed all of my model classes to use this instead of the concrete ObservableCollectionEx class, could I then setup an 'AddOrUpdateImplementation' mapping to force TrackerDog to create ObservableCollectionEx proxies instead of IList proxies?

shaneh20 avatar Sep 07 '18 00:09 shaneh20

@shaneh20 I'm going check it this weekend, so I can give you a good answer to your question :+1:

mfidemraizer avatar Sep 07 '18 11:09 mfidemraizer

@shaneh20 I'm really sorry I've not been able too invest time on this issue. I've had to solve some personal affairs and I couldn't work on this.

mfidemraizer avatar Sep 20 '18 07:09 mfidemraizer

That's cool. I'm not really in any rush

shaneh20 avatar Sep 20 '18 10:09 shaneh20

@shaneh20 Ok, first of all I need to create a .NET Core 2.x-only solution file to let me open and run TrackerDog project and tests in my current setup (Linux) so I can look further on this issue and others.

mfidemraizer avatar Sep 20 '18 12:09 mfidemraizer

This thread seems to have gone quiet, and I have just stumbled upon this exact issue. @mfidemraizer have you got any further news or updates on this? Great work with this library btw!

jnonereacher avatar Mar 14 '19 11:03 jnonereacher

I have the same problem too. I'm extending a HashSet<T>, have created an interface, a concrete class, and a new ChangeInterceptor that extends DefaultCollectionChangeInterceptor<T>

    public interface ISetEx<TEntity> : ICollection<TEntity>, IEnumerable<TEntity>, IEnumerable {
        new bool Add(TEntity item);
        void ExceptWith(IEnumerable<TEntity> other);
        void IntersectWith(IEnumerable<TEntity> other);
        bool IsProperSubsetOf(IEnumerable<TEntity> other);
        bool IsProperSupersetOf(IEnumerable<TEntity> other);
        bool IsSubsetOf(IEnumerable<TEntity> other);
        bool IsSupersetOf(IEnumerable<TEntity> other);
        bool Overlaps(IEnumerable<TEntity> other);
        bool SetEquals(IEnumerable<TEntity> other);
        void SymmetricExceptWith(IEnumerable<TEntity> other);
        void UnionWith(IEnumerable<TEntity> other);
    }

    public class HashSetEx<TEntity> : HashSet<TEntity>, ISetEx<TEntity>, IReadOnlySet<TEntity>, IDeserializationCallback, ISerializable {}

Stoom avatar Mar 07 '21 00:03 Stoom