如何确定一个类在 C# 中是否不可变?
7 回答
有ImmutableObjectAttribute
,但是很少使用并且支持很差-当然也没有强制执行(您可以用[ImmutableObject(true)]
.AFAIK 标记可变对象,这影响的唯一事情是 IDE 处理属性的方式(即显示/不显示命名属性选项)。
实际上,您必须检查FieldInfo.IsInitOnly
,但这仅适用于真正的 100% 不可变类型(假设没有反射滥用等);它无助于冰棒的不变性,也无助于在实践中不可变的事物,但在其实现中却无济于事;即它们不能公开可变,但理论上对象支持它。
这里的一个经典例子是字符串......每个人都“知道”它string
是不可变的......当然,StringBuilder
确实会在引擎盖下改变一个字符串。不,说真的……
鉴于此,很难定义不变性,更不用说稳健地检测它了......
部分问题在于“不可变”可以有多种含义。以 ReadOnlyCollection<T> 为例。
我们倾向于认为它是不可变的。但如果它是 ReadOnlyCollection<SomethingChangeable> 怎么办?此外,由于它实际上只是我传递给构造函数的 IList 的包装器,如果我更改原始 IList 怎么办?
一个好的方法可能是创建一个名称类似于 ReadOnlyAttribute 的属性,并用它标记您认为是只读的类。对于您无法控制的类,您还可以维护您认为不可变的已知类型的列表。
编辑:有关不同类型不变性的一些很好的例子,请阅读 Eric Lippert 的这一系列帖子:http: //blogs.msdn.com/ericlippert/archive/2007/11/13/immutability-in-c-part-one -kinds-of-immutability.aspx
你不能,你只能猜测。如果所有字段都是只读的,那么一旦构造函数完成,实例将是不可变的。这很重要,如果您有以下内容,它将在实例栏中显示为可变的。
class Foo
{
public readonly int X
public readonly int Y
public Foo(int x, int y, Bar bar)
{
this.X = x;
bar.ShowYourself(this);
this.Y = y;
bar.ShowYourself(this);
}
}
但是,如果假定不可变类上的字段是一个集合(并且不是只读的),那么调用不可变类可能是不正确的(因为它的状态可以改变)
请注意,即使所有字段都是只读的,反射也允许修改字段。
检查属性上没有设置器确实是一种非常糟糕的启发式方法。
据我所知,除非明确记录,否则无法确定 C# 中的类是否不可变。
但是,您可以使用反射来检查属性上是否存在 Setter;但是,缺少 setter 并不能保证不变性,因为内部状态可能会更改这些属性的值,无论您是否可以显式设置它们。
此外,再次使用反射检查所有类字段的“IsInitOnly”标志可能表明不变性,但不能保证这一点。
编辑:这里有一个类似的问题,询问关于 Java 语言的问题,其答案也适用于此处。
您从运行时获得的唯一帮助是类中的所有字段是否都用“只读”注释。[编辑,见@ShuggyCoUk] 即使这样,CLR 也会让你重写它。我刚刚验证过了。啊。
您可以通过反射从类中获取 FieldInfo 对象并检查 IsInitOnly。
通过代码,我不确定,但是如果您查看另一种不可变类型的 IL,例如字符串,您将看不到newobj
IL 指令(您将看到 ldstr 用于字符串),可能会检查创建的 IL可能是一种说法,只是猜测......
需要您自担风险使用它。 这在类的循环依赖的情况下不起作用(尽管对其进行修改可能会起作用),并且它可能会给出错误的否定结果(即,如果它返回 true 则意味着该类肯定是不可变的,但如果它返回 false 它可能或者可能不是一成不变的)。但它应该适用于大多数情况,除了非常复杂的类。
using System.Reflection;
public static class StructUtils
{
public static bool IsImmutable(Type type, int depth = 5)
{
if (type == typeof(string) || type.IsValueType)
{
return true;
}
else if (depth == 0)
{
return false;
}
else
{
return type.GetFields()
.Where(fInfo => !fInfo.IsStatic) // Filter out statics
.Where(fInfo => fInfo.FieldType != type) // Filter out 1st level recursion
.All(fInfo => fInfo.IsInitOnly && IsImmutable(fInfo.FieldType, depth - 1)) &&
type.GetProperties()
.Where(pInfo => !pInfo.GetMethod.IsStatic) // Filter out statics
.Where(pInfo => pInfo.PropertyType != type) // Filter out 1st level recursion
.All(pInfo => !SetIsAllowed(pInfo, checkNonPublicSetter: true) && IsImmutable(pInfo.PropertyType, depth - 1));
}
}
private static bool SetIsAllowed(PropertyInfo pInfo, bool checkNonPublicSetter = false)
{
var setMethod = pInfo.GetSetMethod(nonPublic: checkNonPublicSetter);
return pInfo.CanWrite &&
((!checkNonPublicSetter && setMethod.IsPublic) ||
(checkNonPublicSetter && (setMethod.IsPrivate || setMethod.IsFamily || setMethod.IsPublic || setMethod.IsAbstract)));
}
}
我用于测试的一些示例类:
public class Z
{
private readonly List<string> _s;
public int F { get; }
public readonly string K;
public const int Pi = 3;
public Z NextPtr { get; }
}
public class X
{
public Z Z { get; }
public readonly int C;
public string Cmp { get; }
}
public class W
{
public X Z { get; }
public readonly List<int> C1;
public readonly string Cmp1;
}
得到以下结果:
> StructUtils.IsImmutable(typeof(Z))
true
> StructUtils.IsImmutable(typeof(X))
true
> StructUtils.IsImmutable(typeof(W))
false