.net不可变对象

时间:2009-05-08 09:49:13

标签: c# reflection properties

我希望使用以下测试

强制执行我的代码库不可变规则
[TestFixture]
public class TestEntityIf
{
    [Test]
    public void IsImmutable()
    {
        var setterCount =
            (from s in typeof (Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance)
             where s.CanWrite
             select s)
                .Count();

        Assert.That(setterCount == 0, Is.True, "Immutable rule is broken");
    }
}

传递给:

public class Entity
{
    private int ID1;
    public int ID
    {
        get { return ID1; }
    }
}

但不是为了这个:

public class Entity
{
    public int ID { get; private set; }
}

这里有一个问题“WTF?”

8 个答案:

答案 0 :(得分:7)

问题是,由于私人制定者,该属性是公共的 - 因为公共吸气剂 - 并且它是可写的。你必须改进你的测试。

此外我想补充一点,你不能保证这种方式的不变性,因为你可以修改方法中的私有数据。为了保证不变性,您必须检查是否所有字段都是只读的,并且没有自动实现的属性。

public static Boolean IsImmutable(this Type type)
{
    const BindingFlags flags = BindingFlags.Instance |
                               BindingFlags.NonPublic |
                               BindingFlags.Public;

    return type.GetFields(flags).All(f => f.IsInitOnly);
}

public static Boolean IsImmutable(this Object @object)
{
    return (@object == null) || @object.GetType().IsImmutable();
}

使用此扩展方法,您可以轻松地测试类型

typeof(MyType).IsImmutable()

和实例

myInstance.IsImmutable()

因为他们的不变性。

备注

  • 查看实例字段可以让您拥有可写属性,但确保现在有可以修改的字段。
  • 由于私有的匿名支持字段,自动实现的属性将无法通过不可变性测试而失败。
  • 您仍然可以使用反射修改readonly字段。
  • 也许应该检查所有字段的类型是否也是不可变的,因为这些对象属于该状态。
  • 由于可能的周期以及基本类型是可变的,所以对于所有字段都不能使用简单的FiledInfo.FieldType.IsImmutable()

答案 1 :(得分:3)

您可能需要致电

propertyInfo.GetSetMethod().IsPublic 

因为set-method和get-method没有相同的访问修饰符,所以不能依赖PropertyInfo本身。

    var setterCount =
        (from s in typeof (Entity).GetProperties(
           BindingFlags.Public 
           | BindingFlags.NonPublic 
           | BindingFlags.Instance)
         where 
           s.GetSetMethod() != null       // public setter available
         select s)

答案 2 :(得分:3)

对其他地方发布的答案进行小修改。如果至少有一个属性具有受保护或公共setter,则以下内容将返回非零值。请注意检查GetSetMethod返回null(无setter)和IsPrivate测试(即不公开或受保护)而不是IsPublic(仅限公共)。

    var setterCount =
           (from s in typeof(Entity).GetProperties(
              BindingFlags.Public
              | BindingFlags.NonPublic
              | BindingFlags.Instance)
            where
              s.GetSetMethod(true) != null // setter available
              && (!s.GetSetMethod(true).IsPrivate)
            select s).Count();

尽管如此,作为pointed out in Daniel Brückner's answer,一个类没有公开可见的属性设置器这一事实是一个必要的,但不足以使该类被认为是不可变的。

答案 3 :(得分:2)

我认为原因是CanWrite说如果有一个setter它会返回true。私人二传手也是一个二传手。

让我感到惊讶的是,第一次传递,因为它有一个公共设置器,所以除非我仍然太低于cafeine,所以settercount是1所以断言应该失败。它们都应该失败,因为CanWrite只为两者返回true。 (并且linq查询只检索公共属性,包括ID,因为它是公共的)

(编辑)我现在看到你改变了第一个类的代码,所以它不再有一个setter了。

所以你可以假设CanWrite会查看setter方法的访问器,但事实并非如此。你应该这样做:

var setterCount =
            (from s in typeof (Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance)
             where s.GetSetMethod().IsPublic
             select s)
                .Count();

答案 4 :(得分:2)

我建议将物业状况改为:

s.CanWrite && s.GetSetMethod().IsPublic

答案 5 :(得分:1)

肯定是第二个private set

在第一个实例中,类Entity的实例可以通过外部类写入其ID1属性。

在后者中,setter对于类本身是私有的,因此只能从Entity中调用(即它自己的构造函数/初始化器)

在第二个中将private从你的二传手中取出,它应该通过

答案 6 :(得分:1)

如果我理解正确,你希望实体是不可变的。 如果是,则应将测试更改为

var setterCount = (from s in typeof(string).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(p => p.GetSetMethod())
    where s != null && s.IsPublic
    select s).Count();

Assert.That(setterCount == 0, Is.True, "Immutable rule is broken");

答案 7 :(得分:0)

您是否尝试对其进行调试,以查看LINQ查询返回的CanWrite属性为true的属性是什么?显然,它正在拾取你不期望的东西。有了这些知识,您可以使您的LINQ查询更具选择性,以您希望的方式工作。 另外,我同意@Daniel Bruckner的说法,除非字段也是只读的,否则你的课程不是完全可变的。调用者可以调用一个方法或使用一个getter,它可以通过getter在内部修改公开公开的私有字段,这会打破不可变的规则,让客户感到意外,并导致问题。对可变对象的操作不应该有副作用。