【发布时间】:2015-08-17 20:59:46
【问题描述】:
我想实现“做一次”模式,让我避免写三件事:
- 首先声明 var = true
- if(first) Do(...) 重复代码块中的语句
- first = 重复代码块内的错误赋值
我也想避免这样的变通方法:
- 手动维护唯一标识并将其传递给 Do 函数
- 多次定义一个上下文变量
所以我的代码应该像这样简单:
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 行为:
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 的东西来完成。
但如果可以在定义该 lambda 函数并将其用作 id 的源代码中找到文件名和行号,我更愿意。即使定义是复制和粘贴的,这也可以轻松解决不同定义位置具有不同唯一 ID 的问题。
我想一种方法是将 ObjectIDGenerator 用于表示此 lambda 函数的表达式树对象。 ObjectIDGenerator 会为该表达式树返回相同的 id 吗?
另一个例子:如何实现Once类并将嵌套循环变量it2包含在一次中。执行调用以便它只被调用两次-一次为it2 = 4,一次为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类并将外部循环变量包含在一次中。执行调用以便它只被调用3次——一次它= 1,一次它= 2,一次它= 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 的复制和粘贴并且具有相同的实现。
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 函数引用的任何捕获的上下文变量的值。所以
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");
}
无论哪种方式,最后两个示例的目标都是展示如何能够清晰地控制确定唯一性时包含哪些内容,哪些不是。
Jon 这里的解决方案是另一个澄清:
我想将一次和非一次动作的定义保持在相同的顺序中,就好像它们都是非一次的一样,这样我的源代码中 a、b、c 的出现顺序就不必改变,如果我决定是否只写一次 b:
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 和可能其他云数据库就是这种情况),还想象我们还在我们的班级曾经:
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);
);
}
【问题讨论】:
-
只需在序列上调用
Take(1),就不会对第一个之后的任何项目采取行动。 -
如果唯一标识每个操作有问题,您可以将上下文字符串(例如“context1”、“context2”等)传递给 Do 函数。一个简单的哈希集可以跟踪已经执行过一次的上下文。也许不是一个理想的解决方案,但尝试对您的操作的表达式树进行哈希处理以识别它对我来说感觉很重。
-
鉴于我的问题的性质以及带有布尔标志的简单解决方案的可用性,我不是在寻找“不是理想的解决方案”。
-
@alpav 好吧,现在您只是在每次进行更新时更改要求。显然,您还没有仔细考虑所有案例以发现您真正想要的功能。
标签: c# lambda anonymous-function uniqueidentifier