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

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

问题描述

我想实现一次执行"模式,该模式可以避免编写3件事:

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

  • 声明var first = 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行为:

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函数的Expression Tree对象. 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 ?

另一个例子:如何实现一次类并将嵌套循环变量it2包含到一次中,进行调用以使其仅被调用两次-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");
       }
   }

另一个例子:如何实现一次类并将其包含在外部循环变量中一次,请调用它以使其仅被调用3次-一次为它= 1,一次为它= 2,一次为它= 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,并且仅在定义的位置调用了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>对于此解决方案不是必需的(我们可以仅传递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函数的可能增强方法. 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函数的唯一标识.图案?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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