Я работаю над классом тестирования CRUD, потому что мне надоело дублировать те же тестовые шаблоны при проверке сопоставлений NHibernate.
Я переработал код и дошел до того, что все работает так, как я себе представлял, с одним вопиющим раздражением. Все основано на строках, которые используются методами отражения для вызова соответствующих методов репозитория и получения значений соответствующих свойств, таких как идентификаторы объектов.
Это работает, но я уверен, что мне не нужно входить в зло из-за использования строк для таких вещей.
Итак, я начал работать с Linq. Я не тяжелый пользователь Linq, и следующий код меня совершенно озадачил.
Он работает почти отлично (я доберусь почти через секунду), и я очень рад, что он работает, но мне очень хотелось бы знать, почему.
[Test]
public void TestCanUseTesterWithLinqSecondEffort()
{
IRepositoryTestContextManager contextManager = new NHRepositoryTestContextManager();
contextManager.SetUpRepositoryContextAndSchema();
TestInsertMethodWithLinq<NHClientRepository, Client>((x, y) => x.InsertClient(y), _newClient);
contextManager.ResetRepositoryContext();
Client retrievedClient = TestRetrieveMethodWithLinq<NHClientRepository, Client, string>((clientRepository, publicId) => clientRepository.GetClient(publicId), client => client.PublicId, _newClient);
contextManager.TearDownRepositoryContext();
Assert.IsNotNull(retrievedClient);
Assert.AreNotSame(_newClient, retrievedClient);
Assert.AreEqual(_newClient.Id, retrievedClient.Id);
Assert.AreEqual(_newClient.PublicId, retrievedClient.PublicId);
Assert.AreEqual(_newClient.Name, retrievedClient.Name);
}
private void TestInsertMethodWithLinq<TRepositoryType, TEntityType>(Action<TRepositoryType, TEntityType> insertMethod, TEntityType entity)
where TRepositoryType : class, new()
{
insertMethod.Invoke(new TRepositoryType(), entity);
}
private TEntityType TestRetrieveMethodWithLinq<TRepositoryType, TEntityType, TArgumentType>(Func<TRepositoryType, TArgumentType, TEntityType> retrieveMethod, Func<TEntityType, TArgumentType> paramValue, TEntityType theEntity)
where TRepositoryType : class, new()
{
return retrieveMethod.Invoke(new TRepositoryType(), paramValue(theEntity));
}
В частности, речь идет о двух вызываемых делегатах (я знаю, что не нужно вызывать invoke для использования делегата. Я включил его для уточнения). Как компилятор преобразует выражения Linq так, что правильные методы вызывают в недавно созданном классе, в этом случае TRedositoryTypes?
Что касается почти "почти идеально", если в процессе обработки вызванного метода есть исключение, исключение проглатывается. Не знаю, почему это так, но я уже вижу сценарий, когда тесты не завершены, и проблема пропущена, потому что исключения проглатываются.
Почувствуйте это. Спасибо заранее.
Как компилятор преобразует выражения Linq так, чтобы правильные методы вызывались в недавно созданный класс, в этом случае TRedositoryTypes?
Они не являются выражениями Linq (Expression<T>
), просто регулярными ol 'lambdas.
Компилятор просто создает "анонимный" метод в "анонимном" классе для захвата любых переменных. В этом случае у вас нет захваченных переменных - так вот:
TestInsertMethodWithLinq<NHClientRepository, Client>(
(x, y) => x.InsertClient(y), _newClient
);
просто преобразуется в "анонимный" метод:
class __abcde {
public void __fghij(NHClientRepository x, Client y) {
x.InsertClient(y);
}
}
когда вызывающий объект преобразуется в:
var c = new __abcde();
c.__fghij(new NHClientRepository(), _newClient);
Поскольку для вашего общего ограничения требовался конструктор new()
no-args, компилятор смог вставить этот бит new NHClientRepository()
.
Возможно, я что-то пропустил, но считаю, что вы ссылаетесь на дженерики и полиморфизм.
Вы хотите передать тип TRedository и вызвать метод, который могут вызвать все типы TRedository. Волшебство происходит здесь:
TestInsertMethodWithLinq<NHClientRepository, Client>((x, y) => x.InsertClient(y), _newClient);
Вы заявляете, что я собираюсь назвать этот метод и использовать тип NHClientRepository и Client. Затем вы вызываете вызов функции, и общий метод будет использовать NHClient в этой строке:
insertMethod.Invoke(new TRepositoryType(), entity);
Это приведет к тому, что TRepositoryType() будет NHClientRepository. Затем метод вызывает использование типа и действия, которые вы передали, давая вам свой результат.
Возможно, вы захотите поставить точку останова на свой тест и пройти через отладчик во время просмотра стека вызовов, если вы хотите попробовать и следить за тем, что происходит, вы можете посмотреть все общие типы и вызовы методов и т.д., чтобы помочь вам понимаете, что происходит.
РЕДАКТИРОВАТЬ: В отношении вашего комментария вывод типа С# 3 помогает вам в действии. Вывод типа - это то, что отображает x и y как NHClientRepository и Client, а затем позволяет работать. Как компилятор знает во время компиляции, что вы выполняете, он может помочь вам в этом. Вы должны уметь видеть, что intellisense может помочь вам, если вы хотите вызвать insertMethod
без вызова.
Я предполагаю, что это был последний кусок головоломки для вас?
Я не пользователь NHibernate (или любой другой ORM, если на то пошло), поэтому я могу только догадываться. Но я подозреваю, что здесь происходит, что вы используете закрытие. Когда вы создаете закрытие, переменная, которую вы используете в выражении лямбда, захватывается/закрывается/поднимается в класс вместе с методом, так что значение остается текущим, даже если вы не вызываете метод до намного позже.
System.Linq.Enumerable
), и зачем компилятору это нужно » преобразовать "что-нибудь?