仅在 C# 中的超类程序集上允许子类实例化

Allow subclass instantiation only on the assembly of the superclass in C#

想象一下 Xamarin 解决方案中的以下场景:

程序集 A (PCL):

public abstract class MyBaseClass
{
    public MyBaseClass()
    {
        [...]
    }
    [...]
}

程序集 B(第 3 方库):

public class SomeLibClass
{
    [...]
    public void MethodThatCreatesClass(Type classType){
        [...]
        //I want to allow this to work
        var obj = Activator.CreateInstance(classType);
        [...]
    }
    [...]
}

程序集 C(主要项目):

public class ClassImplA:MyBaseClass{
    [...]
}

public class ClassImplA:MyBaseClass{
    [...]
}

public class TheProblem{
    public void AnExample(){
        [...]
        //I want to block these instantiations for this Assembly and any other with subclasses of MyBaseClass
        var obj1 = new ClassImplA()
        var obj2 = new ClassImplB()
        [...]
    }
}

如何防止子classes 在它们自己的程序集上实例化并只允许它们在超级 class 和第 3 方库(使用 Activator.CreateInstance)上实例化?

尝试 1

我虽然可以用 internal constructor 制作基础 class 但是后来,我看到那是多么愚蠢,因为子 classes 将无法继承构造函数,因此它们将无法从 superclass.

继承

尝试 2

我尝试在 class 基础上使用 Assembly.GetCallingAssembly,但这在 PCL 项目中不可用。我找到的解决方案是通过反射来调用它,但它也没有用,因为在两种情况下,基于 class 的结果都是 Assembly C (我认为那是因为谁调用了对于这两种情况,MyBaseClass 的构造函数确实是 ClassImplAClassImplB 的默认构造函数)。

关于如何执行此操作的任何其他想法?或者我在这里遗漏了什么?

更新

想法是让 PCL 程序集从离线同步中抽象出主项目(和一些其他项目)。

鉴于此,我的 PCL 使用自己的数据库进行缓存,我想要的是只为数据库的每个记录提供一个实例(这样当 属性 更改时,所有分配的变量将具有该值,我可以确保因为主项目中没有人能够创建这些 classes 并且它们将由 经理 class 提供给变量] 将处理单个实例)。

因为我为此使用了 SQLite-net,而且它要求每个实例都有一个 空构造函数 ,所以我只需要 允许 SQLite 和 PCL 程序集创建那些在主项目程序集

上声明的子classes

更新 2

如果可以使用 Reflection 绕过解决方案,我没有问题,因为我的主要重点是防止人们通过简单的方式在主项目上做 new ClassImplA错误。但是,如果可能的话,我希望这样 JsonConvert.DeserializeObject<ClassImplA> 这样的东西实际上会因异常而失败。

我可能是错的,但是 access modifiers 的 none 将允许您表达这样的约束 - 它们限制其他实体可以看到的内容,但是一旦他们看到它,他们就可以使用它。

  1. 您可以尝试在基础 class 的构造函数中使用 StackTrace class 来检查调用它的人:

    public class Base
    {
        public Base()
        {
            Console.WriteLine(
                new StackTrace()
                    .GetFrame(1)
                    .GetMethod()
                    .DeclaringType
                    .Assembly
                    .FullName);
        }
    }
    
    public class Derived : Base
    {
        public Derived() {        }
    }
    

处理一些特殊情况后,它可能会与 Activator class 一起使用,但由于显而易见的原因(反射,error-prone string/assembly 处理).

  1. 或者您可以使用一些 dependency 来做任何实质性的事情,并且这种依赖只能由您的主程序集提供:

    public interface ICritical
    {
        // Required to do any real job
        IntPtr CriticalHandle { get; }
    }
    
    public class Base
    {
        public Base(ICritical critical) 
        { 
             if (!(critical is MyOnlyTrueImplementation)) 
                 throw ...  
        }
    }
    
    public class Derived : Base
    {
        // They can't have a constructor without ICritical and you can check that you are getting you own ICritical implementation.
        public Derived(ICritical critical) : base(critical) 
        {        }
    }
    

好吧,其他程序集可能会提供他们对 ICritical 的实现,但只有您的程序集才有用。

  1. 不要试图阻止实体创建 - 无法使用以不当方式创建的实体

假设您可以控制所有生产和使用此类实体的 class 实体,您可以确保只能使用正确创建的实体。

它可以是一个原始的实体追踪机制,甚至是一些dynamic proxy wrapping

public class Context : IDisposable
{
    private HashSet<Object> _entities;

    public TEntity Create<TEntity>()
    {
        var entity = ThirdPartyLib.Create(typeof(TEntity));
        _entities.Add(entity);
        return entity;
    }       

    public void Save<TEntity>(TEntity entity)
    {
        if (!_entities.Contains(entity))
            throw new InvalidOperationException();
        ...;
    }
}

这无助于防止所有错误,但任何坚持 "illegal" 实体的尝试都会被打脸,清楚地表明做错了什么。

  1. 只需将其记录为系统特殊性并保持原样。

一个人不可能总是创造一个non-leaky abstraction(实际上一个人基本上永远不可能)。在这种情况下,解决这个问题似乎要么很重要,要么对性能不利,或者两者同时存在。

因此,与其沉思这些问题,不如记录下所有实体都应通过特殊的 classes 创建。不能保证直接实例化的对象与系统的其余部分一起正常工作。

它可能看起来很糟糕,但以 Entity Framework 及其在 Lazy-Loading 中的 gotchas、代理对象、分离的实体等为例。那是一个 well-known 成熟的库。

我不认为你不应该尝试更好的东西,但这仍然是你可以随时求助的选择。