仅在 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
的构造函数确实是 ClassImplA
和 ClassImplB
的默认构造函数)。
关于如何执行此操作的任何其他想法?或者我在这里遗漏了什么?
更新
想法是让 PCL 程序集从离线同步中抽象出主项目(和一些其他项目)。
鉴于此,我的 PCL 使用自己的数据库进行缓存,我想要的是只为数据库的每个记录提供一个实例(这样当 属性 更改时,所有分配的变量将具有该值,我可以确保因为主项目中没有人能够创建这些 classes 并且它们将由 经理 class 提供给变量] 将处理单个实例)。
因为我为此使用了 SQLite-net,而且它要求每个实例都有一个 空构造函数 ,所以我只需要 允许 SQLite 和 PCL 程序集创建那些在主项目程序集
上声明的子classes
更新 2
如果可以使用 Reflection 绕过解决方案,我没有问题,因为我的主要重点是防止人们通过简单的方式在主项目上做 new ClassImplA
错误。但是,如果可能的话,我希望这样 JsonConvert.DeserializeObject<ClassImplA>
这样的东西实际上会因异常而失败。
我可能是错的,但是 access modifiers 的 none 将允许您表达这样的约束 - 它们限制其他实体可以看到的内容,但是一旦他们看到它,他们就可以使用它。
您可以尝试在基础 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 处理).
或者您可以使用一些 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
的实现,但只有您的程序集才有用。
- 不要试图阻止实体创建 - 无法使用以不当方式创建的实体。
假设您可以控制所有生产和使用此类实体的 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" 实体的尝试都会被打脸,清楚地表明做错了什么。
- 只需将其记录为系统特殊性并保持原样。
一个人不可能总是创造一个non-leaky abstraction(实际上一个人基本上永远不可能)。在这种情况下,解决这个问题似乎要么很重要,要么对性能不利,或者两者同时存在。
因此,与其沉思这些问题,不如记录下所有实体都应通过特殊的 classes 创建。不能保证直接实例化的对象与系统的其余部分一起正常工作。
它可能看起来很糟糕,但以 Entity Framework 及其在 Lazy-Loading 中的 gotchas、代理对象、分离的实体等为例。那是一个 well-known 成熟的库。
我不认为你不应该尝试更好的东西,但这仍然是你可以随时求助的选择。
想象一下 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
的构造函数确实是 ClassImplA
和 ClassImplB
的默认构造函数)。
关于如何执行此操作的任何其他想法?或者我在这里遗漏了什么?
更新
想法是让 PCL 程序集从离线同步中抽象出主项目(和一些其他项目)。
鉴于此,我的 PCL 使用自己的数据库进行缓存,我想要的是只为数据库的每个记录提供一个实例(这样当 属性 更改时,所有分配的变量将具有该值,我可以确保因为主项目中没有人能够创建这些 classes 并且它们将由 经理 class 提供给变量] 将处理单个实例)。
因为我为此使用了 SQLite-net,而且它要求每个实例都有一个 空构造函数 ,所以我只需要 允许 SQLite 和 PCL 程序集创建那些在主项目程序集
上声明的子classes更新 2
如果可以使用 Reflection 绕过解决方案,我没有问题,因为我的主要重点是防止人们通过简单的方式在主项目上做 new ClassImplA
错误。但是,如果可能的话,我希望这样 JsonConvert.DeserializeObject<ClassImplA>
这样的东西实际上会因异常而失败。
我可能是错的,但是 access modifiers 的 none 将允许您表达这样的约束 - 它们限制其他实体可以看到的内容,但是一旦他们看到它,他们就可以使用它。
您可以尝试在基础 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 处理).
或者您可以使用一些 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
的实现,但只有您的程序集才有用。
- 不要试图阻止实体创建 - 无法使用以不当方式创建的实体。
假设您可以控制所有生产和使用此类实体的 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" 实体的尝试都会被打脸,清楚地表明做错了什么。
- 只需将其记录为系统特殊性并保持原样。
一个人不可能总是创造一个non-leaky abstraction(实际上一个人基本上永远不可能)。在这种情况下,解决这个问题似乎要么很重要,要么对性能不利,或者两者同时存在。
因此,与其沉思这些问题,不如记录下所有实体都应通过特殊的 classes 创建。不能保证直接实例化的对象与系统的其余部分一起正常工作。
它可能看起来很糟糕,但以 Entity Framework 及其在 Lazy-Loading 中的 gotchas、代理对象、分离的实体等为例。那是一个 well-known 成熟的库。
我不认为你不应该尝试更好的东西,但这仍然是你可以随时求助的选择。