views:

170

answers:

3

I'm working on a CRUD testing class because I got tired of duplicating the same test patterns when validating my NHibernate mappings.

I've refactored the code and have reached the point where everything is working the way I envisioned it with one glaring irritation. Everything is based on strings which are used by reflection methods to call the appropriate repository methods and get the values of the corresponding properties like entity Id's.

This works but I'm sure I don't need to go into the evils of using strings for such things.

So I started to work with Linq. I'm not a heavy Linq user and the following code has me utterly baffled.

It works almost perfectly (I'll get to the almost in a second) and I'm very happy it works but I would really love to know why.

[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));
    }

Specifically I'm talking about the two invoked delegates (I know one does not have to call invoke to use the delegate. I included it for clarification). How does the compiler transform the Linq expressions so that the correct methods are called on a newly instantiated class, in this case the TRepositoryTypes?

Regarding the almost in "almost perfectly", if there is an exception during the processing of the invoked method, the exception is swallowed. Don't know why that is either but I can already see a scenario where the tests are not complete and a problem is missed because the exceptions are swallowed.

Chew on that. Thanks in advance.

A: 

I'm not an NHibernate user (or any other ORM for that matter), so I can only speculate. But I suspect what's going on here is that you're using a closure. When you create a closure, the variable you use in the lambda expression is captured/closed-over/hoisted into a class along with the method, so that the value stays current even if you don't invoke the method until much later.

Joel Coehoorn
I forgot about the closures. Okay. That helps.
A: 

I might have missed something, but I believe your referring to generics and polymorphism.

You want a TRepository type passed in, and to call a method that all TRepository types can call. The magic happens here:

TestInsertMethodWithLinq<NHClientRepository, Client>((x, y) => x.InsertClient(y), _newClient);

You're stating that I'm going to call this method and use the type NHClientRepository and Client. You then make the function call and the generic method will use NHClient in this line:

insertMethod.Invoke(new TRepositoryType(), entity);

This will resolve TRepositoryType() to be an NHClientRepository. The method then invokes using the type and the action that you passed in, giving you your result.

You might want to put a break point on your test and stepping through with the debugger while watching the call stack if you want to try and follow whats happening, you can look at all the generic types and method calls etc to help you understand whats going on.

EDIT: In reference to your comment, the C# 3 type inference is helping you out with the action. Type inference is what maps x and y to be NHClientRepository and Client, which then allows the call to work. As the compiler knows at compile time what you are performing it is able to assist you with this. You should be able to see that intellisense is able to help you out if you were to call the insertMethod without invoking.

I'm assuming that was the final piece of the puzzle for you?

Spence
I think I got it by combining your answer with Joel's. The closure that Joel reminded me about creates an anonymous class that keeps the lambda variables relevant. The generic arguments are used to allocate the memory needed for that class. So when I pass the instantiated TRepositoryType to the invoke method it's actually stored at the location allocated by the lambda variable which makes it possible for the method under test to be executed. Yes, no maybe???
+1  A: 

How does the compiler transform the Linq expressions so that the correct methods are called on a newly instantiated class, in this case the TRepositoryTypes?

They're not actually Linq expressions (Expression<T>), just regular ol' lambdas.

The compiler just creates an "anonymous" method, in an "anonymous" class to capture any variables. In this case, you have no captured variables - so this:

 TestInsertMethodWithLinq<NHClientRepository, Client>(
    (x, y) => x.InsertClient(y), _newClient
 );

simply gets transformed to an "anonymous" method:

class __abcde {
    public void __fghij(NHClientRepository x, Client y) {
       x.InsertClient(y);
    }
 }

with the caller being transformed to:

 var c = new __abcde();
 c.__fghij(new NHClientRepository(), _newClient);

Since your generic constraint required a new() no-args constructor, the compiler was able to insert that new NHClientRepository() bit.

Mark Brackett
Yep. Got it. We must have been typing at the same time. See comments above. Thanks.