如何在C#中找出一个类是不可变的?
答案 0 :(得分:32)
有ImmutableObjectAttribute
,但很少使用且支持不当 - 当然也没有强制执行(你可以用[ImmutableObject(true)]
标记一个可变对象.AFAIK,这唯一影响的方式就是这种方式IDE处理属性(即显示/不显示命名属性选项)。
实际上,您必须检查FieldInfo.IsInitOnly
,但这仅适用于真正的100%不可变类型(假设没有反射滥用等);它对冰棒不变性没有帮助,也没有在实践中不变的东西,但在实施中却没有;即它们不能被公开变为可变,但理论上该对象支持它。
这里的经典示例是字符串...每个人都“知道”string
是不可变的...当然,StringBuilder
确实在电子琴下变异字符串。不,认真......
鉴于此,很难定义不变性,更不用说强有力地检测它......
答案 1 :(得分:3)
你不能,你只能猜测。 如果所有字段都是只读的,那么一旦构造函数完成,实例将是不可变的。 这很重要,如果您有以下内容,它将显示为实例栏可变。
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);
}
}
但是,如果所谓的不可变类上的字段是一个集合(并且不是只读的)那么调用该类不可变可能是不正确的(因为它的状态可以改变)
请注意,即使所有字段都是只读的,也允许反射修改字段。
检查属性上没有setter确实是一个非常差的启发式。
答案 2 :(得分:3)
部分问题是“不可变”可能有多重含义。举例来说,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
答案 3 :(得分:2)
据我所知,除非明确记录,否则无法确定某个类在C#中是否是不可变的。
但是,您可以使用Reflection来检查属性上是否存在Setter;但是,缺少setter并不能保证不变性,因为内部状态可能会改变这些属性的值,无论你是否可以明确地设置它们。
此外,再次使用Reflection检查所有类字段的'IsInitOnly'标志可能表示不变性,但它不保证它。
编辑:Here's a similar question,询问Java语言,其答案也适用于此。
答案 4 :(得分:1)
从运行时获得的唯一帮助是,如果类中的所有字段都使用“readonly”进行注释。 [编辑,参见@ShuggyCoUk]即便如此,CLR也会让你写下来。我刚刚验证了它。啊。
您可以通过反射从类中获取FieldInfo对象并检查IsInitOnly。
答案 5 :(得分:1)
通过代码,我不确定,但如果你查看另一个不可变类型的IL,例如字符串,你将不看到newobj
IL指令(你)我会看到字符串的ldstr,也许检查创建的IL可能是一种方式来判断,只是猜测......
答案 6 :(得分:0)
使用它需要您自担风险。 在类具有循环依赖性的情况下,这是行不通的(尽管可以对其进行修改),并且它可以给出假否定结果(即,如果返回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