2009-05-08 2 views
5

Я хочу, чтобы обеспечить соблюдение на мой код базового непреложного правила с последующим испытанием.net Неизменные объекты

[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?»

ответ

3

Небольшая модификация ответов, размещенных в других местах. Следующие будут возвращать ненулевые, если есть хотя бы одно свойство с защищенным или общедоступным сетевым устройством. Обратите внимание на то, что GetSetMethod возвращает null (no 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, тот факт, что класс не имеет публично видимых сеттеры собственности является необходимым, но не достаточным условием для данного класса считаются незыблемыми.

+0

Тест s.GetSetMethod (true)! = Null лишний, потому что CanWrite == true гарантирует существование сеттера. –

+0

Истина: «s.GetSetMethod (true)! = Null» эквивалентен CanWrite, являющемуся true, поэтому один из них является избыточным. Я удалил CanWrite, поскольку я хочу быть явным о тестировании для null до разыменования свойства IsPrivate. – Joe

1

Несомненно, это ваш private set во втором.

В первом экземпляре класса Entity может быть присвоено его свойство ID1 внешним классом.

В последнем, сеттер является частным самого класса и так можно назвать только из Сущности (т.е. собственного конструктора/инициализатор)

Возьмите private из вашего сеттер во втором и он должен pass

+0

ID1 - это поле поддержки, а не свойство в первом классе, и оно считывается только через геттер во внешний мир. Как он может быть доступен внешним классом? –

+0

В своем оригинальном примере у него был публичный сеттер в первом имуществе. С тех пор он редактировал вопрос. http://stackoverflow.com/revisions/839066/list –

+0

Я вижу. Спасибо Эону. –

2

Я думаю, причина в том, что CanWrite говорит, что он возвращает true, если есть сеттер. Частный сеттер также является сеттером.

Что меня немного удивляет, так это то, что первый проходит, так как у него есть публичный сеттер, поэтому, если я еще не слишком низко на cafeine, значение settercount равно 1, поэтому утверждение должно потерпеть неудачу. Они оба должны потерпеть неудачу, поскольку CanWrite просто возвращает true для обоих. (и запрос linq просто извлекает общедоступные свойства, включая идентификатор, поскольку он является общедоступным)

(изменить) Теперь я вижу, что вы изменили код первого класса, так что он больше не имеет сеттера.

Итак, ваше предположение, что CanWrite смотрит на аксессуаров метода setter, это не так. Вы должны сделать:

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

Я только что попробовал код, и я согласен. Я получаю счет 1 сеттер для обоих тестовых классов. –

+0

Если я слишком низко на кофейнике, я не вижу сеттера в первом случае. ID1 - это переменная-член, а не свойство, поэтому у нее нет метода «сеттера»? –

+0

точно. Что следует делать TS, это вызов GetSetMethod() в propertyinfo, а затем проверить, истинна ли IsPublic в возвращаемом MethodInfo. –

7

Проблема заключается в том, что свойство является публичным - из-за общественного добытчика - и это записываемые - из частного сеттера. Вам нужно будет уточнить свой тест.

Далее я хочу добавить, что вы не можете гарантировать неизменность таким образом, потому что вы можете изменять личные данные внутри методов. Чтобы гарантировать неизменность, вы должны проверить, что все поля объявлены readonly, и нет автоматических реализованных свойств.

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() для всех полей из-за возможных циклов и потому, что базовые типы изменяемы.
+0

Любые причины использования 'Object' и' Boolean' вместо 'object' и' bool'? –

+0

Они выглядят лучше (подсветка синтаксиса и первая буква первой буквы), и они являются реальными типами и не зависят от того, что генерирует компилятор для строки, bool, int, object и всех остальных (хотя я знаю, что у компилятора нет выбора). –

+0

Есть ли веские причины против этого? –

3

Вы, вероятно, нужно вызвать

propertyInfo.GetSetMethod().IsPublic 

, так как набор-метод и прибудет-метод не имеют один и тот же модификатор доступа, вы не можете полагаться на самом PropertyInfo.

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

+1 Это почти что. Но GetSetMethod() вернет null для частных настроек или свойств без настроек. Вам нужно изменить это, чтобы проверить значение null. – Joe

+0

@Joe: исправлено это, тогда мне больше не нужен CanWrite –

+0

Вам не нужен s.GetSetMethod(). IsPublic тоже, потому что ProeprtyInfo.GetSetMethod() возвращает только общедоступные методы набора, если вы не вызываете ProeprtyInfo. GetSetMethod (Boolean nonPublic). –

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"); 
+0

Правильное утверждение: тип неизменен, если setterCount == 0 является истинным, и в заявлении появится сообщение «Неизменяемое правило нарушено», если этот тест завершился неудачно. –

+0

Исправлено, спасибо Daniel – SeeR

2

Я предлагаю изменения в имущественном состоянии к:

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

Вы пытались отладить ее, чтобы увидеть, какие свойства которых CanWrite свойство верно возвращаются вашим запросом LINQ? Видимо, он подбирает то, чего вы не ожидаете. Благодаря этим знаниям вы можете сделать свой запрос LINQ более избирательным, чтобы работать так, как вы этого хотите. Кроме того, я согласен с @Daniel Bruckner в том, что ваш класс не полностью изменен, если поля не являются также только для чтения. Вызывающий может вызывать метод или использовать геттер, который может внутренне изменять частные поля, которые публично публикуются как только для чтения с помощью геттеров, и это нарушит неизменяемое правило, заставит клиента врасплох и вызовет проблемы. Операции с изменяемым объектом не должны иметь побочных эффектов.

Смежные вопросы