16

如何确定一个类在 C# 中是否不可变?

4

7 回答 7

34

ImmutableObjectAttribute,但是很少使用并且支持很差-当然也没有强制执行(您可以用[ImmutableObject(true)].AFAIK 标记可变对象,这影响的唯一事情是 IDE 处理属性的方式(即显示/不显示命名属性选项)。

实际上,您必须检查FieldInfo.IsInitOnly,但这仅适用于真正的 100% 不可变类型(假设没有反射滥用等);它无助于冰棒的不变性,也无助于在实践中不可变的事物,但在其实现中却无济于事;即它们不能公开可变,但理论上对象支持它。

这里的一个经典例子是字符串......每个人都“知道”它string是不可变的......当然,StringBuilder 确实会在引擎盖下改变一个字符串。不,说真的……

鉴于此,很难定义不变性,更不用说稳健地检测它了......

于 2009-01-27T00:43:58.330 回答
4

部分问题在于“不可变”可以有多种含义。以 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

于 2009-01-27T00:43:12.937 回答
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);
    }
}

但是,如果假定不可变类上的字段是一个集合(并且不是只读的),那么调用不可变类可能是不正确的(因为它的状态可以改变)

请注意,即使所有字段都是只读的,反射也允许修改字段。

检查属性上没有设置器确实是一种非常糟糕的启发式方法。

于 2009-01-27T00:34:43.327 回答
2

据我所知,除非明确记录,否则无法确定 C# 中的类是否不可变。

但是,您可以使用反射来检查属性上是否存在 Setter;但是,缺少 setter 并不能保证不变性,因为内部状态可能会更改这些属性的值,无论您是否可以显式设置它们。

此外,再次使用反射检查所有类字段的“IsInitOnly”标志可能表明不变性,但不能保证这一点。

编辑:这里有一个类似的问题,询问关于 Java 语言的问题,其答案也适用于此处。

于 2009-01-27T00:19:12.703 回答
1

您从运行时获得的唯一帮助是类中的所有字段是否都用“只读”注释。[编辑,见@ShuggyCoUk] 即使这样,CLR 也会让你重写它。我刚刚验证过了。啊。

您可以通过反射从类中获取 FieldInfo 对象并检查 IsInitOnly。

于 2009-01-27T00:20:36.630 回答
1

通过代码,我不确定,但是如果您查看另一种不可变类型的 IL,例如字符串,您将不到newobjIL 指令(您将看到 ldstr 用于字符串),可能会检查创建的 IL可能是一种说法,只是猜测......

于 2009-01-27T00:42:58.863 回答
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
于 2020-02-22T19:48:53.553 回答