J'écrit le code Linq intéressant, mais je ne sais pas comment ou pourquoi cela fonctionne

StackOverflow https://stackoverflow.com/questions/1263831

  •  13-09-2019
  •  | 
  •  

Question

Je travaille sur une classe d'essai CRUD parce que je suis fatigué de dupliquer les mêmes motifs de test lors de la validation de mes correspondances NHibernate.

J'ai refactorisé le code et ont atteint le point où tout fonctionne comme je l'envisageais avec une irritation flagrante. Tout est basé sur des chaînes qui sont utilisées par des méthodes de réflexion pour appeler les méthodes de dépôt appropriées et obtenir les valeurs des propriétés comme entité correspondante Id de.

Cela fonctionne, mais je suis sûr que je ne ai pas besoin d'aller dans les maux d'utiliser des chaînes pour de telles choses.

Alors j'ai commencé à travailler avec LINQ. Je ne suis pas un gros utilisateur LINQ et le code suivant me a complètement dérouté.

Il fonctionne presque parfaitement (je vais à presque une seconde) et je suis très content que ça fonctionne, mais j'aimerais vraiment savoir pourquoi.

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

Plus précisément, je parle des deux délégués (je sais que invoqués on ne dispose pas d'appeler Invoke pour utiliser le délégué. J'inclus pour des précisions). Comment le compilateur transformer les expressions Linq de sorte que les bonnes méthodes sont appelées sur une nouvelle classe instanciée, dans ce cas, les TRepositoryTypes?

En ce qui concerne la presque « presque parfaitement », s'il y a une exception lors du traitement de la méthode invoquée, l'exception est avalée. Je ne sais pas pourquoi cela est non plus, mais je peux déjà voir un scénario où les tests ne sont pas complets et un problème est parce que les exceptions manqué sont avalés.

Chew sur ce point. Merci à l'avance.

Était-ce utile?

La solution

  

Comment le compilateur transformer les expressions Linq de sorte que les bonnes méthodes sont appelées sur une nouvelle classe instanciée, dans ce cas, les TRepositoryTypes?

Ils ne sont pas en fait des expressions Linq (Expression<T>), juste ol » ordinaire lambdas.

Le compilateur crée juste une méthode « anonyme », dans une classe « anonyme » pour capturer toutes les variables. Dans ce cas, vous avez pas de variables capturées - donc ceci:

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

obtient tout simplement transformé en une méthode "anonyme":

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

avec l'appelant se transforme à:

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

Étant donné que votre contrainte générique nécessaire un new() constructeur sans args, le compilateur a pu insérer ce bit new NHClientRepository().

Autres conseils

Je ne suis pas un utilisateur NHibernate (ou tout autre ORM pour cette matière), donc je ne peux que spéculer. Mais je soupçonne que ce qui se passe ici est que vous utilisez une fermeture. Lorsque vous créez une fermeture, la variable que vous utilisez dans l'expression lambda est capturé / fermé-over / hissés dans une classe avec la méthode, de sorte que la valeur reste à jour même si vous ne prononcez pas la méthode que beaucoup plus tard.

Je pourrais avoir manqué quelque chose, mais je crois que votre référence aux médicaments génériques et le polymorphisme.

Vous voulez un type de TRepository transmis, et d'appeler une méthode que tous les types TRepository peuvent appeler. La magie se produit ici:

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

Vous indiquant que je vais appeler cette méthode et utiliser le type NHClientRepository et le client. Vous faites ensuite l'appel de fonction et la méthode générique utiliser NHClient dans cette ligne:

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

Cela permettra de résoudre TRepositoryType () d'être un NHClientRepository. La méthode invoque ensuite en utilisant le type et l'action que vous avez passé, vous donnant le résultat.

Vous pouvez mettre un point d'arrêt sur votre test et pas à pas dans le débogueur tout en regardant la pile d'appels si vous voulez essayer de suivre ce qu'il se passe, vous pouvez regarder tous les types génériques et appelle la méthode etc pour vous aider comprendre ce qui se passe.

EDIT: En référence à votre commentaire, l'inférence de type C # 3 vous aide avec l'action. L'inférence de type est ce que des cartes x et y être NHClientRepository et le client, ce qui permet ensuite l'appel à travailler. Comme le compilateur sait au moment de la compilation ce que vous effectuez est en mesure de vous aider à cela. Vous devriez être en mesure de voir que IntelliSense est en mesure de vous aider si vous deviez appeler le insertMethod sans invoquer.

Je suppose que ce fut le dernier morceau du casse-tête pour vous?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top