【问题标题】:Allow subclass instantiation only on the assembly of the superclass in C#仅在 C# 中的超类的程序集中允许子类实例化
【发布时间】:2017-06-06 09:40:15
【问题描述】:

想象一下 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()
        [...]
    }
}

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

尝试 1

虽然我可以使用internal constructor 创建基类,但后来我发现这是多么愚蠢,因为子类无法继承构造函数,因此它们无法从超类继承.

尝试 2

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

关于如何做到这一点的任何其他想法?还是我在这里遗漏了什么?

更新

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

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

由于我为此使用SQLite-net,并且因为它要求每个实例都有一个空构造函数,我需要一种方法来仅允许创建 SQLite 和 PCL 程序集在主项目程序集上声明的那些子类

更新 2

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

【问题讨论】:

  • 通过一些限制,您可以使构造函数在内部受到保护,并使第 3 方组装成为朋友。但是......如果他们明确检查公共默认 ctor,那么这将失败。如果派生类显式声明了一个公共默认 ctor,那么它就会失败。如果他们用反射覆盖了你所有的安全检查,那么它就会失败......
  • @AdrianoRepetti 请查看我关于 Reflection 案例的更新。关于protected internal,子类可以使用它吗?因为 afaik,base() 对他们来说是不可用的,因为它对主项目程序集不可见,对吧?
  • Protected 将使其对子类和内部朋友程序集可见。老实说,我不确定 Activator.CreateInstance 将如何管理它。
  • @AdrianoRepetti 我只是尝试将MyBaseClass 的构造函数更改为protected internal,但这仍然允许我在它们自己的程序集中实例化子类。我错过了什么?作为记录,子类将不受我控制,在我的示例测试用例中,它们没有声明构造函数(这使它们使用默认构造函数)

标签: c# inheritance subclass .net-assembly instantiation


【解决方案1】:

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

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

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

通过一些特殊情况处理,它可能适用于 Activator class ,但由于明显的原因(反射、容易出错的字符串/程序集处理),它不是最佳解决方案。

  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. 不要试图阻止实体创建 - 禁止使用以不正当方式创建的实体

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

可以是原始的实体追踪机制,甚至可以是一些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();
        ...;
    }
}

这无助于防止所有错误,但任何坚持“非法”实体的尝试都会在脸上炸开,清楚地表明一个人做错了什么。

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

一个人不可能总是创建一个non-leaky abstraction(实际上一个人基本上永远不可能)。在这种情况下,解决这个问题似乎不是微不足道的,就是对性能不利,或者两者兼而有之。

因此,与其考虑这些问题,我们可以只记录应该通过特殊类创建所有实体。直接实例化的对象不能保证与系统的其余部分一起正常工作。

它可能看起来很糟糕,但是以延迟加载中带有gotchas 的实体框架、代理对象、分离实体等为例。那是一个众所周知的成熟库。

我并不是说你不应该尝试更好的东西,但这仍然是你可以随时使用的选择。

【讨论】:

  • 我搜索了StackTrace,但每个人都说它不利于性能。无论如何,我更新了描述以反映我为什么需要这个的原因。而且由于 SQLite 总是使用空构造函数,我不能使用第二种方法吗?
  • @CristianoSantos Bad for performance 取决于您构建这些对象的频率。如果它是每个应用程序运行一次构建的一些高级依赖项,那么它通常是可以接受的价格。尽管如果系统中的每个 DTO 在其构建过程中都会使用反射(就像您的新编辑似乎暗示的那样),那么这可能不是一个好主意(另一方面,应该始终测量任何与性能相关的东西 - 它是尽管如此,实际性能下降可能是可以接受的)。
猜你喜欢
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
  • 2013-12-15
  • 1970-01-01
  • 1970-01-01
  • 2017-10-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多