joelweiss/ChangeTracking

Support for internal constructors

mattbrailsford opened this issue · 4 comments

I'm looking at using ChangeTracking in a project but currently I have entities that have required constructor args on my public API. I don't mind exposing an internal constructor for ChangeTracking, but I'd rather not allow it for general users of my library.

I've had a look and it does appear that by setting InternalsVisibleTo("DynamicProxyGenAssembly2") in your library so that the Castle DynamicProxy can see your constructors, and then swapping any instances of Activator.CreateInstance<T>() within the change tracking code to (T)Activator.CreateInstance(typeof(T), true); does allow all the unit tests to pass so means it can use just internal constructors.

Would it be acceptable to use CreateInstance signature that can use non public constructors? or can you foresee any issues in doing so?

PS I tried setting InternalsVisibleTo("ChangeTracking") but that doesn't seem to allow Activator.CreateInstance to see the internal constructor so I'm guessing my suggestion above would be required.

Further testing of this and it works quite nicely as you can do the following with your constructors and it still works:

[Obsolete("Used by the Change Tacker only. Use one of the public constructors instead.", true)]
internal Order()
{ }

public Order(int id)
{
    Id = id;
    OrderDetails = new List<OrderDetail>();
}

With this you can forcibly prevent people using the parameterless constructor, but still allow it to be proxied and created via Activator.CreateInstance

Hmm, thinking about it, I guess I could just have a public constructor with the obsolete attribute, although that does expose an implementation detail to a user 🤔

Try setting in your assembly [InternalsVisibleTo("DynamicProxyGenAssembly2")]

here is a working sample

using ChangeTracking;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

namespace TestInternalTracking
{
    class Program
    {
        static void Main(string[] args)
        {
            var order = new Order(1);
            var trackable = order.AsTrackable();
            Console.WriteLine($"Trackable Id = {trackable.Id}");
            Console.ReadKey();
        }
    }

    public class Order
    {
        internal Order() { }

        public Order(int id)
        {
            Id = id;
            OrderDetails = new List<OrderDetail>();
        }

        public virtual int Id { get; set; }
        public virtual IList<OrderDetail> OrderDetails { get; set; }
    }

    public class OrderDetail
    {
        public virtual int Id { get; set; }
    }
}

Hey Joel.

This works for the basic proxying, but things like GetOriginal won't work as it uses Activator.CreateInstance<T>() to create the poco and this won't see the internal constructor.