代码之家  ›  专栏  ›  技术社区  ›  Erik

我写了有趣的LINQ代码,但我不知道它是如何工作的,也不知道为什么工作的。

  •  2
  • Erik  · 技术社区  · 16 年前

    我正在编写一个CRUD测试类,因为我厌倦了在验证我的NHibernate映射时复制相同的测试模式。

    我对代码进行了重构,并达到了我想象中的所有事情都能正常工作的程度,这让我非常恼火。所有内容都基于字符串,反射方法使用这些字符串调用适当的存储库方法并获取相应属性(如实体ID)的值。

    这是可行的,但我确信我不需要陷入使用字符串处理此类事情的罪恶之中。

    所以我开始和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表达式,以便在新实例化的类上调用正确的方法,在本例中是trepositorytypes?

    对于“几乎完全”中的,如果在处理调用的方法期间出现异常,则会忽略该异常。也不知道为什么会这样,但是我已经看到了一个场景,其中测试没有完成,并且由于异常被吞没而错过了一个问题。

    细嚼慢咽。事先谢谢。

    3 回复  |  直到 16 年前
        1
  •  2
  •   Mark Brackett    16 年前

    编译器如何转换LINQ表达式,以便在新实例化的类上调用正确的方法,在本例中是trepositorytypes?

    它们实际上不是LINQ表达式( Expression<T> ,只是普通的羊羔肉。

    编译器只是在“匿名”类中创建一个“匿名”方法来捕获任何变量。在这种情况下,您没有捕获变量-因此:

     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() 没有参数构造函数,编译器能够插入 new NHClientRepository() 比特。

        2
  •  0
  •   Joel Coehoorn    16 年前

    我不是一个NHibernate用户(或任何其他的ORM),所以我只能猜测。但我怀疑这里发生的事情是你用了一个封闭。当您创建一个闭包时, 变量 在lambda表达式中使用的是随方法一起捕获/关闭/提升到类中的,这样,即使在很晚才调用该方法,该值也将保持当前状态。

        3
  •  0
  •   Spence    16 年前

    我可能遗漏了一些东西,但我相信您提到的是泛型和多态性。

    您希望传入一个trepositive类型,并调用一个所有trepositive类型都可以调用的方法。魔法发生在这里:

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

    您要说明的是,我将调用此方法并使用类型nhclientrepository和client。然后进行函数调用,泛型方法将在此行中使用nhclient:

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

    这将把trepositoryType()解析为nhclientrepository。然后,该方法使用您传入的类型和操作进行调用,并给出结果。

    您可能希望在测试中设置一个断点,并在观察调用堆栈的同时使用调试器进行调试。如果您希望尝试跟踪发生的情况,可以查看所有泛型类型和方法调用等,以帮助您了解正在发生的情况。

    编辑:参考您的评论,C 3类型推理帮助您完成操作。类型推断将x和y映射为nhclientrepository和client,这样调用就可以工作了。正如编译器在编译时知道的那样,它能够帮助您完成这项工作。如果您要调用 insertMethod 不调用。

    我假设这是你的最后一块拼图?