Ответ 1
Это может быть несколько полезно:
Общей проблемой на любом языке является утверждение, что параметры, отправленные в метод, отвечают вашим требованиям, а если нет, то отправлять приятные информационные сообщения об ошибках. Этот код повторяется снова и снова, и мы часто пытаемся создать для него помощники. Однако в С# кажется, что эти помощники вынуждены иметь дело с некоторым дублированием, навязанным нам языком и компилятором. Чтобы показать, что я имею в виду, позвольте мне представить некоторый сырой код без помощников, за которым следует один из возможных помощников. Затем я укажу на дублирование в помощнике и точно сформулирую свой вопрос.
Во-первых, код без каких-либо помощников:
public void SomeMethod(string firstName, string lastName, int age)
{
if(firstName == null)
{
throw new WhateverException("The value for firstName cannot be null.");
}
if(lastName == null)
{
throw new WhateverException("The value for lastName cannot be null.");
}
// Same kind of code for age, making sure it is a reasonable range (< 150, for example).
// You get the idea
}
}
Теперь код с разумной попыткой помощника:
public void SomeMethod(string firstName, string lastName, int age)
{
Helper.Validate( x=> x !=null, "firstName", firstName);
Helper.Validate( x=> x!= null, "lastName", lastName);
}
Основной вопрос: Обратите внимание, как код должен передать значение параметра и имя параметра ( "firstName" и firstName). Это значит, что сообщение об ошибке может сказать: "Бла-бла-бла-значение для параметра firstName". Вы нашли какой-либо способ обойти это с помощью отражения или чего-нибудь еще? Или способ сделать это менее болезненным?
И в общем, нашли ли вы какие-либо другие способы оптимизации этой задачи проверки параметров при одновременном уменьшении дублирования кода?
EDIT: я читал, что люди говорили об использовании свойства Parameters, но так и не нашли способ дублирования. Кому-нибудь повезло с этим?
Спасибо!
Это может быть несколько полезно:
Вы должны проверить Контракты кода; они делают в значительной степени то, что вы просите. Пример:
[Pure]
public static double GetDistance(Point p1, Point p2)
{
CodeContract.RequiresAlways(p1 != null);
CodeContract.RequiresAlways(p2 != null);
// ...
}
Ничего себе, я нашел здесь что-то действительно интересное. Крис выше дал ссылку на другой вопрос о переполнении стека. Один из ответов там указал на сообщение в блоге, в котором описывается, как получить код следующим образом:
public static void Copy<T>(T[] dst, long dstOffset, T[] src, long srcOffset, long length)
{
Validate.Begin()
.IsNotNull(dst, "dst")
.IsNotNull(src, "src")
.Check()
.IsPositive(length)
.IsIndexInRange(dst, dstOffset, "dstOffset")
.IsIndexInRange(dst, dstOffset + length, "dstOffset + length")
.IsIndexInRange(src, srcOffset, "srcOffset")
.IsIndexInRange(src, srcOffset + length, "srcOffset + length")
.Check();
for (int di = dstOffset; di < dstOffset + length; ++di)
dst[di] = src[di - dstOffset + srcOffset];
}
Я не уверен, что это лучший , но это, безусловно, интересно. Здесь сообщение в блоге, от Рика Брюстера.
Я решил эту проблему несколько недель назад, подумав, что странно, как для библиотек тестирования требуется миллион различных версий Assert
, чтобы сделать их сообщения дескриптивными.
Краткое резюме - с учетом этого бита кода:
int x = 3;
string t = "hi";
Assert(() => 5*x + (2 / t.Length) < 99);
Функция My Assert может распечатать следующую сводку того, что ей передано:
(((5 * x) + (2 / t.Length)) < 99) == True where
{
((5 * x) + (2 / t.Length)) == 16 where
{
(5 * x) == 15 where
{
x == 3
}
(2 / t.Length) == 1 where
{
t.Length == 2 where
{
t == "hi"
}
}
}
}
Таким образом, все имена и значения идентификатора и структура выражения могут быть включены в сообщение об исключении без необходимости их повторения в цитированных строках.
Хорошо, ребята, это я снова, и я нашел что-то еще, что удивительно и восхитительно. Это еще одно сообщение в блоге, на которое ссылается другой вопрос SO, упомянутый выше Крисом.
Этот подход позволяет вам написать это:
public class WebServer
{
public void BootstrapServer( int port, string rootDirectory, string serverName )
{
Guard.IsNotNull( () => rootDirectory );
Guard.IsNotNull( () => serverName );
// Bootstrap the server
}
}
Обратите внимание: нет строки, содержащей "rootDirectory", и строки, содержащие "имя_сервера"!!. И все же его сообщения об ошибках могут сказать что-то вроде "Параметр rootDirectory не должен быть нулевым".
Это именно то, чего я хотел и больше, чем я надеялся. Здесь ссылка на сообщение блога парня.
И реализация довольно проста:
public static class Guard
{
public static void IsNotNull<T>(Expression<Func<T>> expr)
{
// expression value != default of T
if (!expr.Compile()().Equals(default(T)))
return;
var param = (MemberExpression) expr.Body;
throw new ArgumentNullException(param.Member.Name);
}
}
Обратите внимание, что это использует "статическое отражение", поэтому в жесткой петле или что-то еще вы можете использовать подход Рика Брюстера выше.
Как только я опубликую это, я проголосую за Криса и ответ на другой вопрос. Это хороший материал!!!
Использование моей библиотеки The Helper Trinity:
public void SomeMethod(string firstName, string lastName, int age)
{
firstName.AssertNotNull("firstName");
lastName.AssertNotNull("lastName");
...
}
Также поддерживает утверждение, что параметры перечисления верны, коллекции и их содержимое не null
, строковые параметры не пустые и т.д. Подробные примеры см. В документации пользователя здесь.
Вот мой ответ на проблему. Я называю это "" Защитные когти". Он использует парсер IL из Lokad Shared Libs, но имеет более простой подход к формулировке фактических предложений охраны:
string test = null;
Claws.NotNull(() => test);
Вы можете увидеть больше примеров его использования в specs.
Поскольку он использует реальные lambdas в качестве входных данных и использует IL Parser только для генерации исключения в случае нарушения, он должен лучше работать на "счастливом пути", чем проекты, основанные на выражении, в других местах этих ответов.
Ссылки не работают, вот URL: http://github.com/littlebits/guard_claws/
Lokad Shared Libraries также использует реализацию синтаксического анализа на основе IL, которая позволяет избежать дублирования имени параметра в строке.
Например:
Enforce.Arguments(() => controller, () => viewManager,() => workspace);
Будет выдано исключение с соответствующим именем параметра, если любой из перечисленных аргументов равен null. Он также имеет действительно опрятную реализацию правил на основе правил.
например.
Enforce.Argument(() => username, StringIs.Limited(3, 64), StringIs.ValidEmail);
Мое предпочтение состояло бы в том, чтобы просто оценить условие и передать результат, а не передавать оцениваемое выражение и параметр для его оценки. Кроме того, я предпочитаю иметь возможность настраивать все сообщение. Обратите внимание, что это просто предпочтения - я не говорю, что ваш образец неправильный, но есть некоторые случаи, когда это очень полезно.
Helper.Validate( firstName != null || !string.IsNullOrEmpty(directoryID),
"The value for firstName cannot be null if a directory ID is not supplied." );
В этом случае вместо использования собственного типа исключения или действительно общих типов, таких как ApplicationException.. Я думаю, что лучше использовать встроенные типы исключений, специально предназначенные для этого использования:
Среди тех.. System.ArgumentException, System.ArgumentNullException...
Не знаю, переводит ли этот метод из C/С++ в С#, но я сделал это с помощью макросов:
#define CHECK_NULL(x) { (x) != NULL || \ fprintf(stderr, "The value of %s in %s, line %d is null.\n", \ #x, __FILENAME__, __LINE__); }
Это не применяется повсеместно, но во многих случаях это может помочь:
Я предполагаю, что "SomeMethod" выполняет некоторую поведенческую операцию над данными "фамилия", "имя" и "возраст". Оцените свой текущий дизайн кода. Если три части данных плачут за класс, поместите их в класс. В этом классе вы также можете поместить свои чеки. Это освободит "SomeMethod" от проверки ввода.
Конечный результат будет примерно таким:
public void SomeMethod(Person person)
{
person.CheckInvariants();
// code here ...
}
Вызов будет примерно таким (если вы используете .NET 3.5):
SomeMethod(new Person { FirstName = "Joe", LastName = "White", Age = 12 });
в предположении, что класс будет выглядеть следующим образом:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public void CheckInvariants()
{
assertNotNull(FirstName, "first name");
assertNotNull(LastName, "last name");
}
// here are your checks ...
private void assertNotNull(string input, string hint)
{
if (input == null)
{
string message = string.Format("The given {0} is null.", hint);
throw new ApplicationException(message);
}
}
Вместо синтаксического сахара .NET 3.5 вы также можете использовать аргументы конструктора для создания объекта Person.
Как контраст, этот сообщение Miško Hevery в Блог Google Testing утверждает, что такая проверка параметров не всегда может быть хорошей. В результате дискуссии в комментариях также поднимают некоторые интересные моменты.