2015-06-03 2 views
0

я хочу реализовать «делать, когда» модель, которая позволяет мне не писать 3 вещи:Как получить уникальный идентификатор функции лямбда для шаблона «сделать один раз»?

  • объявляющие вар первый = истинный
  • если (первый) Do (...) заявление внутри повторяемого блока кода
  • первое = ложное назначение внутри повторного блока кода

Я также хочу, чтобы избежать обходных путей, как эти:

  • вручную сохраняя и прохождение уникальной идентификации в функцию 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 навсегда y в этой реализации:

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

Как получить уникальный идентификатор прошедшей функции лямбда? Я думаю, что это можно сделать, пройдя его как Дерево выражений и вычисляя хэш или иначе сериализуя его во что-то, что можно поместить в HashSet.

Но я бы предпочел, чтобы в исходном коде можно было найти имя файла и номер строки, который определяет эту лямбда-функцию и использует ее как идентификатор. Это легко разрешило бы проблему разных мест определения, имеющих разные уникальные идентификаторы, даже если было установлено значение &.

Я предполагаю, что одним из способов было бы использовать ObjectIDGenerator для объекта дерева выражений, который представляет эту функцию лямбда. Будет ли ObjectIDGenerator возвращать тот же идентификатор для этого дерева выражений?

Другой пример: как реализовать только класса и включают в себя вложенные петли переменной it2 в once.Do invokation так, что можно было бы назвать лишь дважды - один раз для 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"); 
     } 
    } 

Другой пример: как реализовать один раз класс и включить внешнюю переменную цикла в один раз. Включение, чтобы оно было вызвано только 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"); 
     } 
    } 

Другое уточнение: Если есть вторая функция лямбда, которая определена где-то еще в моем коде, я хочу, чтобы у нее был другой идентификатор, даже если это копиявставки первой лямбда и имеет идентичную реализацию.

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

сейчас думать об этом, в идеальном растворе я бы исключить из ид все, что передается в качестве одного из явных параметров, как этот (x,y,z,...)=>... и включает в себя значение все захваченного контекстных переменных, на которые ссылаются этой лямбда-функцией. Так

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

В любом случае цель 2 последних примеров, чтобы показать, как быть в состоянии чисто контролировать то, что входит в определение уникальности, а что нет ,

адресация решение от Джона здесь еще одно уточнение:

Я хочу, чтобы сохранить определение один раз и не один раз действия в той же последовательности, как если бы они были все не один раз, так что порядок появления, б, в моем исходном коде не должен быть изменена, если я решу писать б только один раз или нет:

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

Реалистичных е xample - представьте себе таблицу со многими полями возможных сумм, представьте, что я не могу сгенерировать скрипт и выполнить его в одной партии (что имеет место с Azure и, возможно, другими облачными базами данных), также представьте, что мы также определяем метод AfterFirst в нашем классе 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); 
      ); 
      } 
+0

Просто вызовите 'Take (1)' на последовательности, чтобы не воздействовать на любых предметах после первого. – Servy

+0

Если уникальная идентификация каждого действия проблематична, вы можете просто передать в контекстную строку (например, «context1», «context2» и т. Д.) Вашу функцию Do. Простой hashset мог отслеживать контексты, которые уже выполнялись один раз. Возможно, это не идеальное решение, но попытка хешировать дерево выражений вашего действия, чтобы идентифицировать его, просто кажется мне тяжелым. – RogerN

+0

Учитывая характер моего вопроса с наличием простого решения с булевыми флагами, я не ищу «не идеальное решение». – alpav

ответ

0

Я только что обнаружил, изящное и простое решение:

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

Как вы можете видеть, требуется идентификатор лямбда-функции является Кортеж (файл, строка).

Конечно, мы могли бы оптимизировать использование памяти, не сохраняя путь к файлу или сохраняя его один раз, но основная проблема получения уникального идентификатора лямбда-функции решается при условии, что она определена только там, где она вызывается и вызывается только там, где она определена. Это предположение справедливо, учитывая примеры использования шаблона «сделать один раз».

Expression<Action> не требуется для этого решения (мы могли бы передать только действие), но его можно использовать для сканирования параметров и захваченных контекстных переменных внутри этого выражения и включения их значений в определение идентификатора лямбда-функции.

Потенциальных усовершенствования идентификации функций лямбды путем сканирования дерева выражений могут быть получены из ответов на этот вопрос Most efficient way to test equality of lambda expressions, который был обнаружен DLeh

+0

Если кто-то удивляется, что passed.New(), вот определение: [DebuggerStepThrough] общественности статические T Новые (это Т @this, PARAMS объект [] арг) { возвращение (T) Activator.CreateInstance (TypeOf (T), args); } – alpav

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

Здесь я бы определил это как операция с точки зрения самого перечислим. Рассмотрим:

public static void OnceAndAll<T>(this IEnumerable<T> source, Action<T> once, Action<T> all) 
{ 
    using(var en = source.GetEnumerator()) 
    if(en.MoveNext()) 
    { 
     var current = en.Current; 
     once(current); 
     all(current); 
     while(en.MoveNext()) 
     all(en.Current); 
    } 
} 
public static void Once<T>(this IEnumerable<T> source, Action<T> once) 
{ 
    using(var en = source.GetEnumerator()) 
    if(en.MoveNext()) 
     once(en.Current); 
} 
//Overrides for where the value is not actually used: 
public static void OnceAndAll<T>(this IEnumerable<T> source, Action once, Action<T> all) 
{ 
    source.OnceAndAll(_ => once(), all); 
} 
public static void OnceAndAll<T>(this IEnumerable<T> source, Action<T> once, Action all) 
{ 
    source.OnceAndAll(once, _ => all()); 
} 
public static void OnceAndAll<T>(this IEnumerable<T> source, Action once, Action all) 
{ 
    source.OnceAndAll(once, _ => all()); 
} 
public static void Once<T>(this IEnumerable<T> source, Action once) 
{ 
    source.Once(_ => once()); 
} 

Теперь я могу сделать:

new[]{ 1, 2, 3 }.OnceAndAll(
() => Console.Write("It will write once and only once"), 
    it => new[]{4,5}.Once(() => Console.Write("Inner loop will write 3 times and only 3 times: {0}", it)) 
); 

Здесь первый Action передается OnceAndAll выполняется только один раз, но второй выполняется каждый раз, когда для этой первой последовательности. Он, в свою очередь, устанавливает Action для использования во второй последовательности из 2 элементов только один раз для каждой последовательности.

Варианты его использования также может обрабатывать много других ваших дел, но не ваш первый:

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

Для этого я бы что-то, что создает новое действие:

public static Action Once(this Action toDoOnce) 
{ 
    return() => { 
    if(toDoOnce != null) 
     toDoOnce(); 
    toDoOnce = null; 
    }; 
} 
public static Action<T> Once<T>(this Action<T> toDoOnce) 
{ 
    return obj => { 
    if(toDoOnce != null) 
     toDoOnce(obj); 
    toDoOnce = null; 
    }; 
} 
public static Action<T1, T2> Once<T1, T2>(this Action<T1, T2> toDoOnce) 
{ 
    return (arg1, arg2) => { 
    if(toDoOnce != null) 
     toDoOnce(arg1, arg2); 
    toDoOnce = null; 
    }; 
} 
/* and so on … */ 
public static Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> Once<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(this Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> toDoOnce) 
{ 
    return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16) => { 
    if(toDoOnce != null) 
     toDoOnce(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); 
    toDoOnce = null; 
    }; 
} 

И затем используйте его как таковое:

Action outer =() => Console.Write("It will write once and only once"); 
var outerOnce = outer.Once(); 
var innerOnce = ((Action)(()=>Console.Write("Inner loop will write once and only once"))).Once(); 
foreach(var it in new[]{1,2,3}) 
{ 
    outerOnce(); 
    foreach(var it2 in new[]{4,5}) 
     innerOnce(); 
} 

Это означает определение «единства» вне цикла, но тогда это wh на «onceness» в область охвата, которая содержит цикл. (За один раз в жизни приложения у нас уже есть простые подходы, основанные на static).

+0

Я хочу держать одно и несколько раз в той же последовательности, как если бы все они были не однократно, поэтому порядок записи (a); Write (b); Write (c) не нужно разбивать на wb = Write (b); Написать); термометр(); Напишите (c) в случае, если я хочу преобразовать Write (b) в одно действие. – alpav

+0

Второй подход, который я даю, может быть использован для этого. –