Подтвердить что ты не робот

Программно вставить вызов метода для каждого свойства класса

Мой вопрос основан на этой статье.

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

У меня есть интерфейс, который следует за этой конструкцией

public interface IFreezableModel
{
    void Freeze();
    bool IsFrozen{get;}
}

цель состоит в том, чтобы убедиться, что после вызова метода Freeze свойство IsFrozen имеет значение True и свойства объекта больше не могут быть изменены.

Чтобы упростить, я буду использовать абстрактный базовый класс:

public abstract class BaseFreezableModel : IFreezableModel
{
    public void Freeze()
    {
        _isFrozen = true;
    }
    public bool IsFrozen
    { 
       get {return _isFrozen;}            
    }
    protected ThrowIfFrozen()
    {
       if (IsFrozen)
           throw new Exception("Attempted to change a property of a frozen model");
    }
}

Таким образом, у меня может быть класс вроде

public class MyModel : BaseFreezableModel
{
     private string _myProperty;
     public string MyProperty
     {
        get{return _myProperty;}
        set 
        {
           ThrowIfFrozen();
           _myProperty = value;
        }
     }
 }

Это все хорошо и просто, но какую стратегию я могу принять, чтобы убедиться, что все свойства следуют шаблону выше? (помимо написания сеттеров и геттеров)

Это альтернативы, которые я придумал:

  • Найдите механизм для ввода метода в каждый набор свойств, используя, возможно, испускающий. Но я не знаю, как это сделать, с какими потенциальными проблемами я могу столкнуться (и, следовательно, сколько времени это займет). Если кто-то знает это и может указать мне в этом направлении, это было бы здорово.

  • Используйте некоторые шаблоны во время процесса сборки, чтобы "Call to OnCheckFrozen" был вставлен непосредственно перед компиляцией. Преимущество этого заключается в том, что он действительно прост для понимания и может работать для подобных сценариев.

  • Найдите фреймворк, который может сделать все это для меня, но это так же, как крайний случай, поскольку мне не разрешено использовать внешнюю инфраструктуру для этого проекта.

Какие решения вы бы использовали для этого?

4b9b3361

Ответ 1

Вы входите в мир Aspect Oriented Programming здесь. Вы могли бы сбить эту функциональность за 5 минут, используя PostSharp - но, похоже, вам не разрешено использовать внешние фреймворки. Таким образом, ваш выбор сводится к реализации вашей собственной очень простой инфраструктуры AOP или просто кусает пулю и добавляет проверки для каждого средства настройки свойств.

Лично я бы просто написал чек в настройщике свойств. Это может быть не так болезненно, как вы ожидаете. Вы можете написать фрагмент кода визуальной студийной студии, чтобы ускорить процесс. Вы также можете написать класс unit test, который, используя отражение, просматривал бы все свойства замороженного объекта и пытался установить значение - с помощью теста если не было исключено исключение.

EDIT В ответ на запрос VoodooChilds.. Вот краткий пример класса unit test, используя NUnit и отличную библиотеку FluentAssertions.

[TestFixture]
public class PropertiesThrowWhenFrozenTest
{
    [TestCase(typeof(Foo))]
    [TestCase(typeof(Bar))]
    [TestCase(typeof(Baz))]
    public void AllPropertiesThrowWhenFrozen(Type type)
    {
        var target = Activator.CreateInstance(type) as IFreezable;

        target.Freeze();

        foreach(var property in type.GetProperties())
        {
            this.AssertPropertyThrowsWhenChanged(target, property);
        }
    }

    private void AssertPropertyThrowsWhenChanged(object target, PropertyInfo property)
    {
        // In the case of reference types, setting the property to null should be sufficient
        // to test the behaviour...
        object value = null;

        // In the case of value types, just create a default instance...
        if (property.PropertyType.IsValueType)
            value = Activator.CreateInstance(property.PropertyType);

        Action setter = () => property.GetSetMethod().Invoke(target, new object[] { value });

        // ShouldThrow is a handy extension method of the FluentAssetions library...
        setter.ShouldThrow<InvalidOperationException>();
    }
}

Этот метод использует параметризованный unit test для прохождения в тестируемых типах, но вы также можете инкапсулировать весь этот код в общий базовый класс (где T: IFreezable) и создавать расширенные классы для каждого тестируемого типа, но некоторые тестовые бегуны не любят тесты в базовых классах. * ahem * Resharper! ahem

EDIT 2 и, просто для удовольствия, вот пример Gherkin script, который можно использовать для создания гораздо более гибких тестов для такого рода вещей:)

Feature: AllPropertiesThrowWhenFrozen
    In order to make sure I haven't made any oversights in my code
    As a software developer
    I want to be able to assert that all properties of a class throw an exception when the object is frozen

Scenario: Setting the Bar property on the Foo type
  Given I have an instance of the class MyNamespace.MyProject.Foo
    And it is frozen
  When I set the property Bar with a value of 10
  Then a System.InvalidOperationException should be thrown

Ответ 3

как сказал Мэтт с добавлением написания правила FxCop для проверки вызова метода

Ответ 4

как насчет дополнительного бита косвенности с использованием прокси-шаблона, чтобы вы могли вставить замороженный чек? если объект, к которому относится прокси-сервер, - это замороженный бросок, если не продолжить. однако это означает, что вам нужен прокси-сервер для каждого IFreezableModel (хотя, возможно, дженерики могут это преодолеть), и он будет применяться для каждого члена класса, к которому вы обращаетесь (или прокси требует больше сложностей).