Я написал интересный код Linq, но понятия не имею, как и почему он работает

2

Я работаю над классом тестирования 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?

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

Почувствуйте это. Спасибо заранее.

  • 0
    Я не понимаю вопроса - что вы подразумеваете под «выражениями LINQ» в первую очередь (я не вижу здесь ни ключевых слов LINQ, ни каких-либо методов System.Linq.Enumerable ), и зачем компилятору это нужно » преобразовать "что-нибудь?
  • 0
    Извините, уже поздно. Вы правы. Я имел в виду лямбда-выражение, о котором я читал в книге Линка. Получил Linq на мой усталый мозг.
Теги:
generics
delegates
invoke

3 ответа

2
Лучший ответ

Как компилятор преобразует выражения 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().

  • 0
    Ага. Понял. Должно быть, мы печатали одновременно. Смотрите комментарии выше. Благодарю.
0

Возможно, я что-то пропустил, но считаю, что вы ссылаетесь на дженерики и полиморфизм.

Вы хотите передать тип 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 без вызова.

Я предполагаю, что это был последний кусок головоломки для вас?

  • 0
    Я думаю, что получил его, объединив ваш ответ с ответом Джоэла. Закрытие, о котором Джоэл напомнил мне, создает анонимный класс, который поддерживает релевантные лямбда-переменные. Общие аргументы используются для выделения памяти, необходимой для этого класса. Поэтому, когда я передаю созданный экземпляр TRepositoryType методу invoke, он фактически сохраняется в месте, выделенном лямбда-переменной, что делает возможным выполнение тестируемого метода. Да нет Может быть???
0

Я не пользователь NHibernate (или любой другой ORM, если на то пошло), поэтому я могу только догадываться. Но я подозреваю, что здесь происходит, что вы используете закрытие. Когда вы создаете закрытие, переменная, которую вы используете в выражении лямбда, захватывается/закрывается/поднимается в класс вместе с методом, так что значение остается текущим, даже если вы не вызываете метод до намного позже.

  • 0
    Я забыл о закрытиях. Хорошо. Что помогает.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню