.net Неизменяемые объекты - PullRequest
5 голосов
/ 08 мая 2009

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

[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 ]

7 голосов
/ 08 мая 2009

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

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

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() для всех полей из-за возможных циклов и из-за того, что базовые типы являются изменяемыми.
3 голосов
/ 08 мая 2009

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

Тем не менее, как указывает в ответе Даниэля Брюкнера , тот факт, что класс не имеет общедоступных установщиков свойств, является необходимым, но не достаточным условием для того, чтобы класс считался неизменным.

3 голосов
/ 08 мая 2009

Вам, вероятно, нужно позвонить

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 голосов
/ 08 мая 2009

Я предлагаю изменить состояние недвижимости на:

s.CanWrite && s.GetSetMethod().IsPublic
2 голосов
/ 08 мая 2009

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

Что меня удивляет, так это то, что первые проходы, так как у него есть общедоступный сеттер, так что, если у меня все еще слишком мало кофеина, 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();
1 голос
/ 08 мая 2009

Если я вас правильно понял, вы хотите, чтобы Сущность была неизменной. Если это так, то тест должен быть изменен на

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");
1 голос
/ 08 мая 2009

Конечно, это ваш private set во втором.

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

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

Возьмите private из вашего сеттера за секунду, и он должен пройти

0 голосов
/ 08 мая 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...