У меня есть часть программного обеспечения, написанная с использованием свободного синтаксиса. Цепочка методов имеет определенное «окончание», перед которым на самом деле ничего полезного в коде не делается (например, NBuilder или генерация запросов Linq-to-SQL, фактически не затрагивающая базу данных, пока мы не перебираем наши объекты, скажем, с помощью ToList () ).
Проблема, с которой я столкнулся, заключается в том, что среди других разработчиков возникает путаница по поводу правильного использования кода. Они пренебрегают вызовом метода "окончания" (таким образом, никогда "ничего не делают")!
Меня интересует принудительное использование возвращаемого значения некоторых моих методов, чтобы мы никогда не могли "завершить цепочку", не вызвав этот метод "Finalize ()" или "Save ()" это на самом деле делает работу.
Рассмотрим следующий код:
//The "factory" class the user will be dealing with
public class FluentClass
{
//The entry point for this software
public IntermediateClass<T> Init<T>()
{
return new IntermediateClass<T>();
}
}
//The class that actually does the work
public class IntermediateClass<T>
{
private List<T> _values;
//The user cannot call this constructor
internal IntermediateClass<T>()
{
_values = new List<T>();
}
//Once generated, they can call "setup" methods such as this
public IntermediateClass<T> With(T value)
{
var instance = new IntermediateClass<T>() { _values = _values };
instance._values.Add(value);
return instance;
}
//Picture "lazy loading" - you have to call this method to
//actually do anything worthwhile
public void Save()
{
var itemCount = _values.Count();
. . . //save to database, write a log, do some real work
}
}
Как видите, правильное использование этого кода будет выглядеть примерно так:
new FluentClass().Init<int>().With(-1).With(300).With(42).Save();
Проблема в том, что люди используют его таким образом (думая, что он достигает того же, что и выше):
new FluentClass().Init<int>().With(-1).With(300).With(42);
Эта проблема настолько распространена, что с совершенно благими намерениями другой разработчик однажды фактически изменил имя метода Init, чтобы указать, что этот метод выполнял «реальную работу» программного обеспечения.
Логические ошибки, подобные этим, очень трудно обнаружить, и, конечно, они компилируются, поскольку вполне допустимо вызывать метод с возвращаемым значением и просто «притворяться», что он возвращает void. Visual Studio не волнует, если вы это сделаете; Ваше программное обеспечение все еще будет компилироваться и запускаться (хотя в некоторых случаях я считаю, что оно выдает предупреждение). Это отличная возможность, конечно. Представьте себе простой метод «InsertToDatabase», который возвращает идентификатор новой строки в виде целого числа - легко видеть, что в некоторых случаях нам нужен этот идентификатор, а в некоторых случаях мы можем обойтись без него.
В случае этого программного обеспечения, безусловно, нет никаких причин отказываться от этой функции «Сохранить» в конце цепочки методов. Это очень специализированная утилита, и единственное преимущество - последний шаг.
Я хочу, чтобы чье-то программное обеспечение не работало на уровне компилятора , если они вызывают «With ()», а не «Save ()».
Традиционными средствами это кажется невыполнимой задачей, но именно поэтому я и прихожу к вам, ребята. Есть ли Атрибут, который я могу использовать, чтобы предотвратить "приведение к пустоте" метода или что-то подобное?
Примечание: Альтернативный способ достижения этой цели, который мне уже предлагался, - это написать набор юнит-тестов для обеспечения соблюдения этого правила и использовать что-то вроде http://www.testdriven.net для их привязки. компилятору. Это приемлемое решение, но я надеюсь на что-то более элегантное.