如何为“执行一次"获取 lambda 函数的唯一 ID?图案? [英] How to get unique id of lambda function for "do once" pattern?

查看:25
本文介绍了如何为“执行一次"获取 lambda 函数的唯一 ID?图案?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想实现做一次"模式,让我避免写三件事:

I want to implement "do once" pattern that allows me to avoid writing 3 things:

  • 首先声明 var = true
  • if(first) Do(...) 语句在重复的代码块内
  • first = 重复代码块内的错误赋值

我也想避免这样的解决方法:

I also want to avoid workarounds like these:

  • 手动维护唯一标识并将其传递给 Do 函数
  • 多次定义一个上下文变量

所以我的代码应该看起来像这样简单:

So my code should look as simple as this:

using(var once = new Once())
   foreach(var it in new[]{1,2,3}){
       once.Do(()=>Console.Write("It should write once and only once"));
       Console.Write("It should write 3 times");
       foreach(var it2 in new[]{4,5}){
               once.Do(()=>Console.Write("Inner loop should write once and only once"));
               Console.Write("It should write 6 times");
       }
   }

或者这个:

using(var once = new Once())
   foreach(var it in new[]{1,2,3}){
       once.Do(()=>Console.Write("It should write once and only once"));
       Console.Write("It should write 3 times");
       once.Do(()=>Console.Write("It should write once, but only after first instance (out of 3) of previous write."));
       foreach(var it2 in new[]{4,5}){
           once.Do(()=>Console.Write("Inner loop should write once and only once"));
           Console.Write("It should write 6 times");
           once.Do(()=>Console.Write("It should write once, but only after first instance (out of 6) of previous write."));
       }
       Console.Write("Repeated ending should appear 3 times");
   }

如果我使用 ObjectIDGenerator,它不能解决我的问题,因为它为这个实现实现中的每个调用提供了不同的 Id for Action act:

If I use ObjectIDGenerator, it does not solve my problem because it gives different Id for Action act for every call in this implementation implementation:

public class Once : IDisposable{
    HashSet<long> passed;
    static ObjectIDGenerator idgen = new ObjectIDGenerator();

    public Once(){
        passed = passed.New();
    }

    public bool Do(Action act){
        if(act != null){
            bool firstTime;
            var id = idgen.GetId(act,out firstTime);
            if(!passed.Contains(id)){
                act();
                passed.Add(id);
                return true;
            }
            else
                return false;
        }
        else
            return false;
    }

    void IDisposable.Dispose() {
        passed.Clear();
    }
}

如何获取传递的 lambda 函数的唯一 ID?我认为可以通过将其作为表达式树遍历并计算哈希或以其他方式将其序列化为可以放入 HashSet 的内容来完成.

How to get unique id of passed lambda function ? I think it can be done by traversing it as Expression Tree and calculating hash or otherwise serializing it into something that can be placed into HashSet.

但我更喜欢是否可以在定义该 lambda 函数的源代码中找到文件名和行号并将其用作 id.即使复制粘贴定义,也可以轻松解决定义不同位置具有不同唯一 id 的问题.

But I would prefer if it was possible to find file name and line number in source code that defines that lambda function and use it as id. That would easily solve problem of different places of definition having different unique ids even if definition was copy&pasted.

我想一种方法是将 ObjectIDGenerator 用于表示此 lambda 函数的表达式树对象.ObjectIDGenerator 会为那个表达式树返回相同的 id 吗?

I guess one way would be to use ObjectIDGenerator for Expression Tree object that represents this lambda function. Would ObjectIDGenerator return same id for that Expression Tree ?

另一个例子:如何实现Once类并将嵌套循环变量it2包含到once中.执行调用以便它只被调用两次——一次是it2 = 4,一次是it2 = 5:

Another example: how to implement Once class and include nested loop variable it2 into once.Do invokation so that it would be called only twice - once for it2 = 4 and once for it2 = 5:

using(var once = new Once())
   foreach(var it in new[]{1,2,3}){
       once.Do(()=>Console.Write("It should write once and only once"));
       Console.Write("It should write 3 times");
       foreach(var it2 in new[]{4,5}){
               once.Do(()=>Console.Write("Inner loop should write twice and only twice: {0}", it2));
               Console.Write("It should write 6 times");
       }
   }

另一个例子:如何实现Once类并将外部循环变量包含在once中.执行调用以便它只被调用3次——一次for it = 1,一次for it = 2,一次for it=3:

Another example: how to implement Once class and include outer loop variable it into once.Do invokation so that it would be called only 3 times - once for it = 1, once for it = 2 and once for it=3:

using(var once = new Once())
   foreach(var it in new[]{1,2,3}){
       once.Do(()=>Console.Write("It should write once and only once"));
       Console.Write("It should write 3 times");
       foreach(var it2 in new[]{4,5}){
               once.Do(()=>Console.Write("Inner loop should write 3 times and only 3 times: {0}", it));
               Console.Write("It should write 6 times");
       }
   }

另一个说明:如果在我的代码中的其他地方定义了第二个 lambda 函数,我希望它具有不同的 id,即使它是第一个 lambda 的复制和粘贴并且具有相同的实现.

Another clarification: If there is second lambda function that is defined somewhere else in my code, I want it to have different id even if it's a copy&paste of first lambda and has identical implementation.

using(var once = new Once())
   foreach(var it in new[]{1,2,3}){
       once.Do(()=>Console.Write("It should write twice because it's defined in different lines of code"));
       once.Do(()=>Console.Write("It should write twice because it's defined in different lines of code"));
       Console.Write("It should write 3 times");
   }

现在考虑一下,在理想的解决方案中,我会从 id 中排除任何作为显式参数之一传递的内容,例如 (x,y,z,...)=>... 并包括该 lambda 函数引用的任何捕获的上下文变量的值.所以

Now thinking about it, in an ideal solution I would exclude from id anything that is passed as one of explicit parameters like this (x,y,z,...)=>... and include values of any captured context variables referenced by that lambda function. So

using(var once = new Once())
   foreach(var it in new[]{1,2,3}){
       once.Do((arg)=>Console.Write("It should write once {0}",arg));
       once.Do(()=>Console.Write("It should write 3 times {0}",it));
       Console.Write("It should write 3 times");
   }

或者倒置更好:

using(var once = new Once())
   foreach(var it in new[]{1,2,3}){
       once.Do(()=>Console.Write("It should write once {0}",it));
       once.Do((arg)=>Console.Write("It should write 3 times {0}",arg));
       Console.Write("It should write 3 times");
   }

无论哪种方式,最后两个示例的目标都是展示如何能够干净地控制哪些内容包含在唯一性的确定中,哪些内容不包含在内.

Either way the goal of last 2 examples is to show how be able to control cleanly what is included into determination of uniqueness and what is not.

Jon 提出的解决方案是另一个说明:

Addressing solution from Jon here is another clarification:

我想将一次性和非一次性动作的定义保持在相同的顺序,就好像它们都是非一次一样,这样我的源代码中 a,b,c 的出现顺序就不必更改,如果我决定只写一次 b 与否:

I want to keep definition of once and non-once actions in the same sequence as if they were all non-once, so that order of appearance of a,b,c in my source code does not have to be changed if I decide to write b only once or not:

using(var once = new Once())
   foreach(var it in new[]{1,2,3}){
       Console.Write("a");
       Console.Write("b");
       Console.Write("c");
   }

不必更改:

using(var once = new Once())
   foreach(var it in new[]{1,2,3}){
       Console.Write("a");
       once.Do(()=>Console.Write("b"));
       Console.Write("c");
   }

现实例子 - 想象一个有许多可能的金额字段的表,想象我不能生成脚本并批量执行它(Azure 和其他云数据库就是这种情况),还想象我们也在其中定义了 AfterFirst 方法我们班一次:

Realistic example - imagine some table with many possible amount fields, imagine that I cannot generate script and execute it in one batch (which is the case with Azure and probably other cloud databases), also imagine that we also define AfterFirst method in our class Once:

using(var tblOnce = new Once())
    foreach(var tbl in db.Tables)
       using(var fldOnce = new Once())
          foreach(var fld in tbl.Fields){
            fldOnce.Do(        ()=>conn.Exec(" CREATE TABLE {0}({1} {2})",tbl.Name, fld.Name, fld.SqlType));
            if(fld.Name.EndsWith("Amount"))
                fldOnce.Do(    ()=>conn.Exec(" ALTER TABLE {0} ADD Total money", tbl.Name));
            fldOnce.AfterFirst(()=>conn.Exec(" ALTER TABLE {0} ADD {1} {2}", tbl.Name, fld.Name, fld.SqlType));
            if(fld.PrimaryKey)
                fldOnce.Do(    ()=>conn.Exec(" ALTER TABLE {0} ADD CONSTRAINT PK_{0}_{1} PRIMARY KEY CLUSTERED({1})", tbl.Name, fld.Name));
            fldOnce.Do(()=>
                tblOnce.Do(    ()=>conn.Exec(" CREATE TABLE Tables (name varchar(50))"));
                conn.Exec("                    INSERT tables (name) select " + tbl.Name);
            );
          }

推荐答案

我刚刚发现了优雅而简单的解决方案:

I just discovered elegant and simple solution:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;

public class Once : IDisposable
{
    HashSet<Tuple<string,int>> passed;

    public Once(){
        passed = passed.New();
    }

    public bool Do(Expression<Action> act,
        [CallerFilePath] string file = "",
        [CallerLineNumber] int line = 0
    ){
        if(act != null){
            var id = Tuple.Create(file,line);
            if(!passed.Contains(id)){
                act.Compile().Invoke();
                passed.Add(id);
                return true;
            }
        }
        return false;
    }

    void IDisposable.Dispose() {
        passed.Clear();
    }
}

如您所见,lambda 函数所需的 id 是 Tuple(file,line).

As you can see, the needed id of lambda function is Tuple(file,line).

当然,我们可以通过不存储文件路径或存储一次来优化内存使用,但是假设它仅在被调用的地方定义并且仅在定义的地方被调用,那么获取 lambda 函数的唯一 id 的主要问题就解决了.给定使用一次"模式的示例,该假设是有效的.

Of course, we could optimize memory usage by not storing file path or storing it once, but the main problem of getting unique id of lambda function is solved assuming that it's defined only where it's invoked and invoked only where it's defined. That assumption is valid given examples of usage of "do once" pattern.

Expression 对于这个解决方案不是必需的(我们可以只传递 Action),但它可以用于扫描该表达式中的参数和捕获的上下文变量,并将它们的值包含在确定中lambda 函数 ID.

Expression<Action> is not necessary for this solution (we could pass just Action), but it can be used to scan for parameters and captured context variables inside that expression and include their values into determination of lambda function id.

通过对表达式树的扫描来识别 lambda 函数的潜在增强可以从这个问题的答案中获得 最有效的方法来测试 lambda 表达式的相等性DLeh 发现>

Potential enhancements of identification of lambda functions through scanning of expression tree can be derived from answers to this question Most efficient way to test equality of lambda expressions which was discovered by DLeh

这篇关于如何为“执行一次"获取 lambda 函数的唯一 ID?图案?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆