Методы расширения, это слишком грязно?


Я люблю лямбды, и функциональное программирование и т. д. Но иногда я задаюсь вопросом, если я возьму его слишком далеко..

public static T With<T>(this T source, Action<T> action)
{
    action(source);
    return source;
}

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
    return source.Select(x => x.With(action));
}

мысли?

Обновление

Некоторые положительные моменты, некоторые я разделяю, некоторые нет, но несколько деталей:

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

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

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

public static IEnumerable<T> WithEach<T>(this IEnumerable<T> source, Action<T> action)
{
    foreach(var x in source)
        yield return x.With(action);
}

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

this.SomeObservable = someObservable
    .With(o => o.Subscribe(this.OnNext)
        .With(this._disposables.Add));


2689
7
задан 15 июля 2011 в 03:07 Источник Поделиться
Комментарии
7 ответов

Мой совет: Не делай этого.


  1. Строительной техники, которые предлагаем вам написать выражения, которые полезны только для их побочных эффектов плохом функциональном стиле.

  2. Вы как-то используете "выбрать", когда вы используете его как деталь реализации "по каждому элементу". "Выбор" не имеет семантики "Еогеасп"! Цель "выберите", чтобы вычислить проекцию, чтобы не вызвать побочный эффект.

  3. Вы строите себе маленькую бомбу замедленного действия. Потому что ваша реализация foreach-это построить запрос SELECT, в "Еогеасп" исполняет лениво. Когда вы говорите "бла-бла.По каждому элементу(любой);" что не делать ничего. Он просто создает объект, который представляет делаешь побочных эффектов в будущем. Поскольку вы никогда не использовать этот объект для ничего, побочные эффекты остаются недоделанными. Если, напротив, вы используете этот объект, вы рискуете, используя ее в два раза и вызывает те же побочные эффекты снова.

Так сказать, идея , что вы делаете звук, идея, представляющая собой последовательность стороне-осуществление операций в лениво-оценка объекта как побочные эффекты представлены в Haskell. Но если это то, что вы хотите сделать, то на самом деле построить себе реальные монады, которые четко и unambiguosly представляет семантику "я строю объект, представляющий собой последовательность побочных эффектов в будущем". Ты скромничаешь об этом! Быть ясно, что это то, что ты делаешь.

26
ответ дан 15 июля 2011 в 03:07 Источник Поделиться

Я не думаю, что это действительно обеспечивает большое значение за пределами существующего .Выберите() проекции. Все, что вы пишете в ваших действий вы также можете написать в .Выберите() лямбда.

Например:

myItems.Select(item => 
{
item.DoSomethingWeird();
return item;
}).Select(item =>
{
item.DoSomethingWeird(); //again
return item;
});

Кроме того, я бы не присвоить объекту имя. У него есть и другие, противоположные средства. "Во()" Может быть более подходящим именем.

2
ответ дан 15 июля 2011 в 03:07 Источник Поделиться

Перечисляя что-то, используя это расширение не должно вызывать побочных эффектов.

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

myList.Foreach(x => Console.WriteLine(x));

Как вы не использовать результат, нет ничего, что тянет итерации, так что это ничего не перебирать вообще. Это будет просто создать код, который способен итерации, и выбросить.

2
ответ дан 15 июля 2011 в 03:07 Источник Поделиться

Я предполагаю, что EricLippert имел в виду нечто подобное, когда говорит:


Так сказать, идея , что вы делаете звук, идея, представляющая собой последовательность стороне-осуществление операций в лениво-оценка объекта как побочные эффекты представлены в Haskell. Но если это то, что вы хотите сделать, то на самом деле построить себе реальные монады, которые четко и unambiguosly представляет семантику "я строю объект, представляющий собой последовательность побочных эффектов в будущем".

//was ForEach
/// <summary>
/// Causes some action when the element in the enumeration is touched.
/// The action will only happen the first time the element is touched.
/// </summary>
/// <typeparam name="T">The type of element in the enumerable</typeparam>
/// <param name="source">The enumerable source</param>
/// <param name="action">the action to execute</param>
/// <returns>An enumerable representing the source which will execute the action when elements are enumerated</returns>
public static IEnumerable<T> OnEnumeration<T>(this IEnumerable<T> source, Action<T> action)
{
return new FutureSideEffect<T>(source, x => x.With(action));
}

public class FutureSideEffect<T> : IEnumerable<T>, IEnumerator<T>
{
readonly IEnumerable<T> enumerable;
readonly List<T> enumerated = new List<T>();
readonly Stack<IEnumerator<T>> enumeratorStack = new Stack<IEnumerator<T>>();

public FutureSideEffect(IEnumerable<T> enumerable, Action<T> action)
{
this.enumerable = enumerable.Select(x =>
{
action(x);
return x;
}
);
}

public IEnumerator<T> GetEnumerator()
{
return this;
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

public bool MoveNext()
{
IEnumerator<T> enumerator = null;
if (enumeratorStack.Count != 0)
{
enumerator = enumeratorStack.Peek();
if (enumerator == null)
{
enumeratorStack.Pop();
enumerator = enumerated.GetEnumerator();
enumeratorStack.Push(enumerator);
}
}
else
{
enumerator = enumerable.GetEnumerator();
enumeratorStack.Push(enumerator);
}
var result = enumerator.MoveNext();
if (!result && enumeratorStack.Count > 1)
{
enumeratorStack.Pop();
return MoveNext();
}
return result;
}
public void Reset()
{
IEnumerator<T> enumerator = null;
if (enumeratorStack.Count != 0)
{
enumerator = enumeratorStack.Peek();
}
if (enumerator == null)
{
return;
}
enumeratorStack.Push(null);
}
public T Current
{
get
{
var current = enumeratorStack.Peek().Current;
if (enumeratorStack.Count == 1)
{
enumerated.Add(current);
}
return current;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

//finalizer unnecessary because there are no unmanaged resources
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
enumerated.Clear();
while (enumeratorStack.Count != 0)
{
var enumerator = enumeratorStack.Pop();
if (enumerator != null)
{
enumerator.Dispose();
enumerator = null;
}
}
//don't touch enumerable because I don't own it
}
}
}

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

2
ответ дан 27 августа 2012 в 10:08 Источник Поделиться

Потому что список уже определяет Еогеасп() в качестве метода типа Void, я бы не хотел, что перепутали с "Еогеасп()" это тоже выбор().

1
ответ дан 15 июля 2011 в 03:07 Источник Поделиться

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

Путем добавления расширения методы любого объекта, вы портите полезность технологии IntelliSense. Вы должны избегать этого, потому что это может стать грязным (и запутанной) быстро. В C# уже есть синтаксис для вызова методов, вы должны спросить себя, если вы действительно хотите другой способ выразить то же самое.

В качестве альтернативы рассмотрите функцию высшего порядка:

static Func<T,T> SideEffect(Action<T> action) { return x=>{action(); return x;} }

Которые можно использовать следующим образом:

var lazylist = 
something.Select(
SideEffect(item => Console.WriteLine("iteration logged "+item)));

Обратите внимание, что побочные эффекты делают код гораздо труднее понять; это объясняется в другие ответы.

1
ответ дан 15 июля 2011 в 04:07 Источник Поделиться

Все что противостоит этому?:

foreach(var item in source)
{
//do something?
}

Методы расширения являются большими, когда они не вызывают побочных эффектов, но это будет нарушать стандартный функциональный подход с LINQ.

Он может скрыть точкой вашего цепной способ:

 source.Where(x => x.IsValid)
.With(x => x.MakeInvalid())
.Count();

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

0
ответ дан 15 июля 2011 в 03:07 Источник Поделиться