仅允许在C#

时间:2017-01-21 13:25:13

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

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

装配A(PCL):

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

大会B(第三方图书馆):

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使用自己的数据库进行缓存,我想要的只是为每个DB记录提供一个实例(这样当属性发生变化时,所有分配的变量都将具有该值,我可以确保因为主项目中没有人能够创建这些类,并且它们将由管理器类提供给变量,它将处理单个实例。

由于我正在使用SQLite-net,因为它要求每个实例都有一个空构造函数,所以我需要一种方法只能允许SQLite和PCL程序集用于创建在主项目程序集(ies)上声明的子类

更新2

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

1 个答案:

答案 0 :(得分: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() {        }
    }
    
  2. 通过一些特殊情况处理,它可能适用于Activator类,但由于显而易见的原因(反射,容易出错的字符串/汇编处理)而不是最佳解决方案。

    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) 
          {        }
      }
      
    2. 好吧,其他程序集可能会提供ICritical的实现,但是你的实现是唯一可以做任何好事的程序。

      1. 不要试图阻止实体创建 - 无法使用以不正当方式创建的实体
      2. 假设您可以控制生成和使用此类实体的所有类,您可以确保只能使用正确创建的实体。

        它可以是原始实体跟踪机制,甚至是一些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. 将其记录为作为系统特性,并保持原样。
        2. 一个人不能总是创建一个non-leaky abstraction(实际上一个基本上永远不可能)。在这种情况下,似乎解决这个问题要么是重要的,要么是性能不好,或者两者兼而有之。

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

          它可能看起来很糟糕,但是,例如,实体框架在Lazy-Loading中使用gotchas,代理对象,分离实体等等。这是一个着名的成熟图书馆。

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