你最喜欢的C#扩展方法是什么?(codeplex.com/extensionoverflow)

478

让我们列出一份答案,其中您可以发布您卓越且最喜欢的扩展方法

要求是必须发布完整代码,并提供示例以及如何使用它的解释。

基于对此主题的高度关注,我在Codeplex上建立了一个名为extensionoverflow的开源项目。

请标记您的答案以接受将代码放入Codeplex项目中。

请发布完整的源代码而不是链接。

Codeplex新闻:

2010年08月24日 Codeplex页面现在在这里:http://extensionoverflow.codeplex.com/

2008年11月11日 XmlSerialize / XmlDeserialize已经实现并通过单元测试

2008年11月11日 还有更多开发人员的空间。;-) 现在加入吧!

2008年11月11日 第三个贡献者加入ExtensionOverflow,欢迎BKristensen

2008年11月11日,FormatWith现已实现通过单元测试

2008年11月09日,第二位贡献者加入了ExtensionOverflow。欢迎chakrit

2008年11月09日,我们需要更多的开发人员。;-)

2008年11月09日,ThrowIfArgumentIsNull现已实现通过单元测试在Codeplex上。


现在第一段代码已经提交到 Codeplex 网站。 - bovium
Erik,不幸的是,现在所有的事情都已经在CodePlex上开始了。请无论如何加入。 - bovium
3
看起来还不错。我对静态类的命名有一点评论。将它们命名为<type>Extensions并不是很具指示性。例如,StringExtensions 既包含格式化内容又包含 XML 内容。我认为更好的做法是根据你扩展的类型来命名类。例如,在 UnixDateTimeConversions 中命名。你可以合理地猜测它包含将时间转换为 Unix 时间及从 Unix 时间转换的方法。这只是一个想法! - ecoffey
请查看以下网址了解有关C#扩展方法的更多信息:http://planetofcoders.com/c-extension-methods/ - Gaurav Agrawal
150个回答

6

我通常使用这个扩展方法与匿名类型一起来获取一个类似于Ruby的字典

public static Dictionary<string, object> ToDictionary(this object o)
{
    var dictionary = new Dictionary<string, object>();

    foreach (var propertyInfo in o.GetType().GetProperties())
    {
        if (propertyInfo.GetIndexParameters().Length == 0)
        {
            dictionary.Add(propertyInfo.Name, propertyInfo.GetValue(o, null));
        }
    }

    return dictionary;
}

您可以使用它。
var dummy = new { color = "#000000", width = "100%", id = "myid" };
Dictionary<string, object> dict = dummy.ToDictionary();

同时,使用扩展方法

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
    foreach (T item in source)
    {
        action(item);
    }
}

你可以做到

dummy.ToDictionary().ForEach((p) => Console.Write("{0}='{1}' ", p.Key, p.Value));

输出

颜色='#000000' 宽度='100%' id='myid'


你也可以这样做:Console.Write(dummy.ToDictionary().Select(p => string.Format(p.Key + "='" + p.Value + "'"))); 因此不需要使用ForEach扩展。 - chakrit

5
这是一个位图扩展,可以将位图转换为灰度图像。
public static Bitmap GrayScale(this Bitmap bitmap)
{
    Bitmap newBitmap = new Bitmap(bitmap.Width, bitmap.Height);
    Graphics g = Graphics.FromImage(newBitmap);

    //the grayscale ColorMatrix
    ColorMatrix colorMatrix = new ColorMatrix(new float[][] {
            new float[] {.3f, .3f, .3f, 0, 0},
            new float[] {.59f, .59f, .59f, 0, 0},
            new float[] {.11f, .11f, .11f, 0, 0},
            new float[] {0, 0, 0, 1, 0},
            new float[] {0, 0, 0, 0, 1}
    });

    ImageAttributes attributes = new ImageAttributes();
    attributes.SetColorMatrix(colorMatrix);
    g.DrawImage(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height), 0, 0, bitmap.Width, bitmap.Height, GraphicsUnit.Pixel, attributes);
    g.Dispose();
    return newBitmap;
}

示例用法:

Bitmap grayscaled = bitmap.GrayScale()

不错!你知道如何使用WPF(而不是使用GDI)完成相同的操作吗? - Thomas Levesque

5

以下是我们工作代码库中的一个有趣的例子。在任务线程上遍历一个昂贵的延迟计算可枚举对象,并通过观察者将结果推回。

public static IObservable<T> ToAsyncObservable<T>(this IEnumerable<T> @this)
{
    return Observable.Create<T>(observer =>
    {
        var task = new Task(() =>
        {
            try
            {
                @this.Run(observer.OnNext);
                observer.OnCompleted();
            }
            catch (Exception e)
            {
                observer.OnError(e);
            }
        });

        task.Start();

        return () => { };
    });
}

简单示例:

new DirectoryInfo(@"c:\program files")
    .EnumerateFiles("*", SearchOption.AllDirectories)
    .ToAsyncObservable()
    .BufferWithTime(TimeSpan.FromSeconds(0.5))
    .ObserveOnDispatcher()
    .Subscribe(
        l => Console.WriteLine("{0} received", l.Count),
        () => Console.WriteLine("Done!"));

for (;;)
{
    Thread.Sleep(10);
    Dispatcher.PushFrame(new DispatcherFrame());
}

如果你没有使用精妙的Reactive Extensions,显然这个扩展将对你无用!

更新: 感谢评论区里的Richard指出,这个扩展方法是不必要的。RX已经有了一个接受IScheduler参数的扩展方法"ToObservable",请使用它代替原来的方法!


不确定是否应该使用@this作为参数名称。一方面,由于它是扩展方法,您可以将其视为常规类方法。另一方面,使用一个(常见的)关键字作为参数名称让我感到不舒服。听起来很有趣。 - Davy8
我最初在博客或者可能是在这里的SO上学到了@this。最开始我也有同样的疑虑,但是过去几周我一直在使用它来编写我的所有扩展方法,并且逐渐喜欢上了它。我认为“可以将其视为常规类方法”这一点比关注关键字的重用要强烈得多。我确实考虑过使用“th”或“self”作为名称,但我特别喜欢@符号真正跳出来的方式。它不断地提醒我我正在使用什么类型的方法。 - scobi
嗨,Scott,我对任务或Rx没有太多经验,我很难理解这个方法的实现。当评估序列中的单个项目很昂贵(因此需要异步评估)时,这是否有用?它会为每个项目创建一个新线程还是在推送更多项目时重用相同的作业线程? - xofz
当序列很昂贵时,这非常有用。从池中拉出一个单线程以异步地遍历可枚举对象。它直到可枚举对象完成或抛出异常才返回。从技术上讲,这个示例不需要任何调度程序的东西...我包括它是因为我写了很多WPF代码,这是我的一个频繁模式:发送任务去做某事,将其作为可观察对象发布,通过UI线程的消息队列分派结果。 - scobi
3
为什么 enumerable.ToObservable(Scheduler.TaskPool) 不能解决同样的问题? - Richard Szalay
@Richard - 没有!不知道那个函数。 :) - scobi

5

这里是另一组我发现非常实用的工具:

public static T ObjectWithMin<T, TResult>(this IEnumerable<T> sequence, Func<T, TResult> predicate)
    where T : class
    where TResult : IComparable
{
    if (!sequence.Any()) return null;

    //get the first object with its predicate value
    var seed = sequence.Select(x => new {Object = x, Value = predicate(x)}).FirstOrDefault();
    //compare against all others, replacing the accumulator with the lesser value
    //tie goes to first object found
    return
        sequence.Select(x => new {Object = x, Value = predicate(x)})
            .Aggregate(seed,(acc, x) => acc.Value.CompareTo(x.Value) <= 0 ? acc : x).Object;
}

public static T ObjectWithMax<T, TResult>(this IEnumerable<T> sequence, Func<T, TResult> predicate)
    where T : class
    where TResult : IComparable
{
    if (!sequence.Any()) return null;

    //get the first object with its predicate value
    var seed = sequence.Select(x => new {Object = x, Value = predicate(x)}).FirstOrDefault();
    //compare against all others, replacing the accumulator with the greater value
    //tie goes to last object found
    return
        sequence.Select(x => new {Object = x, Value = predicate(x)})
            .Aggregate(seed, (acc, x) => acc.Value.CompareTo(x.Value) > 0 ? acc : x).Object;
}

使用方法:

var myObject = myList.ObjectWithMin(x=>x.PropA);

这些方法基本上替代了像这样的用法:
var myObject = myList.OrderBy(x=>x.PropA).FirstOrDefault(); //O(nlog(n)) and unstable

并且

var myObject = myList.Where(x=>x.PropA == myList.Min(x=>x.PropA)).FirstOrDefault(); //O(N^2) but stable

并且

var minValue = myList.Min(x=>x.PropA);
var myObject = myList.Where(x=>x.PropA == minValue).FirstOrDefault(); //not a one-liner, and though linear and stable it's slower (evaluates the enumerable twice)

5
static string Format( this string str,
                    , params Expression<Func<string,object>>[] args)
{
    var parameters = args.ToDictionary
                        ( e=>string.Format("{{{0}}}",e.Parameters[0].Name)
                        , e=>e.Compile()(e.Parameters[0].Name));

    var sb = new StringBuilder(str);
    foreach(var kv in parameters)
    {
        sb.Replace( kv.Key
                  , kv.Value != null ? kv.Value.ToString() : "");
    }

    return sb.ToString();
}

通过上述扩展,您可以编写以下内容:

var str = "{foo} {bar} {baz}".Format(foo=>foo, bar=>2, baz=>new object());

然后你会得到"foo 2 System.Object"


关于字符串格式化的性能和尤其是疯狂地使用方法,你可能想要查看Phil Haack的博客,了解不同的实现方式... http://haacked.com/archive/2009/01/14/named-formats-redux.aspx - WestDiscGolf

5

在我看来,这个简单的方法比“Enumerable.Range”更好:

/// <summary>
/// Replace "Enumerable.Range(n)" with "n.Range()":
/// </summary>
/// <param name="n">iterations</param>
/// <returns>0..n-1</returns>
public static IEnumerable<int> Range(this int n)
{
    for (int i = 0; i < n; i++)
        yield return i;
}

5
你们可能已经知道了一种有趣的扩展方法用法,就是作为混入(mixin)的一种形式。一些扩展方法,比如XmlSerializable,几乎会出现在每个类中;但对于大多数类(如ThreadSqlConnection),这并没有意义。

应该将某些功能显式地混入到需要使用它的类中。我提出了一种新的符号表示法来表示这种类型,使用M前缀。

因此,XmlSerializable 就变成了这样:

public interface MXmlSerializable { }
public static class XmlSerializable {
  public static string ToXml(this MXmlSerializable self) {
    if (self == null) throw new ArgumentNullException();
    var serializer = new XmlSerializer(self.GetType());
    using (var writer = new StringWriter()) {
      serializer.Serialize(writer, self);
      return writer.GetStringBuilder().ToString();
    }
  }
  public static T FromXml<T>(string xml) where T : MXmlSerializable {
    var serializer = new XmlSerializer(typeof(T));
    return (T)serializer.Deserialize(new StringReader(xml));
  }
}

一个类然后混入它:
public class Customer : MXmlSerializable {
  public string Name { get; set; }
  public bool Preferred { get; set; }
}

而且使用方法非常简单:

var customer = new Customer { 
  Name = "Guybrush Threepwood", 
  Preferred = true };
var xml = customer.ToXml();

如果您喜欢这个想法,您可以在项目中创建一个新的命名空间来存放有用的混合内容。您觉得呢?
哦,顺便说一下,我认为大多数扩展方法应该显式地测试为空

4

我创建了一个很好的Each扩展,它具有与jQuery的each函数相同的行为。

它允许像下面这样的操作,您可以获取当前值的索引并通过返回false来跳出循环:

new[] { "first", "second", "third" }.Each((value, index) =>
{
    if (value.Contains("d"))
        return false;
    Console.Write(value);
    return true;
});

以下是代码

/// <summary>
/// Generic iterator function that is useful to replace a foreach loop with at your discretion.  A provided action is performed on each element.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="action">Function that takes in the current value in the sequence. 
/// <returns></returns>
public static IEnumerable<T> Each<T>(this IEnumerable<T> source, Action<T> action)
{
    return source.Each((value, index) =>
    {
        action(value);
        return true;
    });
}


/// <summary>
/// Generic iterator function that is useful to replace a foreach loop with at your discretion.  A provided action is performed on each element.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="action">Function that takes in the current value and its index in the sequence.  
/// <returns></returns>
public static IEnumerable<T> Each<T>(this IEnumerable<T> source, Action<T, int> action)
{
    return source.Each((value, index) =>
    {
        action(value, index);
        return true;
    });
}

/// <summary>
/// Generic iterator function that is useful to replace a foreach loop with at your discretion.  A provided action is performed on each element.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="action">Function that takes in the current value in the sequence.  Returns a value indicating whether the iteration should continue.  So return false if you don't want to iterate anymore.</param>
/// <returns></returns>
public static IEnumerable<T> Each<T>(this IEnumerable<T> source, Func<T, bool> action)
{
    return source.Each((value, index) =>
    {
        return action(value);
    });
}

/// <summary>
/// Generic iterator function that is useful to replace a foreach loop with at your discretion.  A provided action is performed on each element.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="action">Function that takes in the current value and its index in the sequence.  Returns a value indicating whether the iteration should continue.  So return false if you don't want to iterate anymore.</param>
/// <returns></returns>
public static IEnumerable<T> Each<T>(this IEnumerable<T> source, Func<T, int, bool> action)
{
    if (source == null)
        return source;

    int index = 0;
    foreach (var sourceItem in source)
    {
        if (!action(sourceItem, index))
            break;
        index++;
    }
    return source;
}

它与TakeWhile有何不同?(除了带有Action的重载) - Thomas Levesque

4

String.As<T> 可以用于将字符串值转换为某种类型(主要用于基元类型和支持 IConvertable 接口的类型)。它可以很好地与 Nullable 类型甚至枚举一起使用!

public static partial class StringExtensions
{
    /// <summary>
    /// Converts the string to the specified type, using the default value configured for the type.
    /// </summary>
    /// <typeparam name="T">Type the string will be converted to. The type must implement IConvertable.</typeparam>
    /// <param name="original">The original string.</param>
    /// <returns>The converted value.</returns>
    public static T As<T>(this String original)
    {
        return As(original, CultureInfo.CurrentCulture,
                  default(T));
    }

    /// <summary>
    /// Converts the string to the specified type, using the default value configured for the type.
    /// </summary>
    /// <typeparam name="T">Type the string will be converted to.</typeparam>
    /// <param name="original">The original string.</param>
    /// <param name="defaultValue">The default value to use in case the original string is null or empty, or can't be converted.</param>
    /// <returns>The converted value.</returns>
    public static T As<T>(this String original, T defaultValue)
    {
        return As(original, CultureInfo.CurrentCulture, defaultValue);
    }

    /// <summary>
    /// Converts the string to the specified type, using the default value configured for the type.
    /// </summary>
    /// <typeparam name="T">Type the string will be converted to.</typeparam>
    /// <param name="original">The original string.</param>
    /// <param name="provider">Format provider used during the type conversion.</param>
    /// <returns>The converted value.</returns>
    public static T As<T>(this String original, IFormatProvider provider)
    {
        return As(original, provider, default(T));
    }

    /// <summary>
    /// Converts the string to the specified type.
    /// </summary>
    /// <typeparam name="T">Type the string will be converted to.</typeparam>
    /// <param name="original">The original string.</param>
    /// <param name="provider">Format provider used during the type conversion.</param>
    /// <param name="defaultValue">The default value to use in case the original string is null or empty, or can't be converted.</param>
    /// <returns>The converted value.</returns>
    /// <remarks>
    /// If an error occurs while converting the specified value to the requested type, the exception is caught and the default is returned. It is strongly recommended you
    /// do NOT use this method if it is important that conversion failures are not swallowed up.
    ///
    /// This method is intended to be used to convert string values to primatives, not for parsing, converting, or deserializing complex types.
    /// </remarks>
    public static T As<T>(this String original, IFormatProvider provider,
                          T defaultValue)
    {
        T result;
        Type type = typeof (T);

        if (String.IsNullOrEmpty(original)) result = defaultValue;
        else
        {
            // need to get the underlying type if T is Nullable<>.

            if (type.IsNullableType())
            {
                type = Nullable.GetUnderlyingType(type);
            }

            try
            {
                // ChangeType doesn't work properly on Enums
                result = type.IsEnum
                             ? (T) Enum.Parse(type, original, true)
                             : (T) Convert.ChangeType(original, type, provider);
            }
            catch // HACK: what can we do to minimize or avoid raising exceptions as part of normal operation? custom string parsing (regex?) for well-known types? it would be best to know if you can convert to the desired type before you attempt to do so.
            {
                result = defaultValue;
            }
        }

        return result;
    }
}

这取决于另一个简单的Type扩展:

/// <summary>
/// Extension methods for <see cref="Type"/>.
/// </summary>
public static class TypeExtensions
{
    /// <summary>
    /// Returns whether or not the specified type is <see cref="Nullable{T}"/>.
    /// </summary>
    /// <param name="type">A <see cref="Type"/>.</param>
    /// <returns>True if the specified type is <see cref="Nullable{T}"/>; otherwise, false.</returns>
    /// <remarks>Use <see cref="Nullable.GetUnderlyingType"/> to access the underlying type.</remarks>
    public static bool IsNullableType(this Type type)
    {
        if (type == null) throw new ArgumentNullException("type");

        return type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof (Nullable<>));
    }
}

使用方法:

var someInt = "1".As<int>();
var someIntDefault = "bad value".As(1); // "bad value" won't convert, so the default value 1 is returned.
var someEnum = "Sunday".As<DayOfWeek>();
someEnum = "0".As<DayOfWeek>(); // returns Sunday
var someNullableEnum = "".As<DayOfWeek?>(null); // returns a null value since "" can't be converted

4

我经常在可空数字中使用它。它可以帮助捕获那些被0、NaN、Infinity除的错误...

public static bool IsNullOrDefault<T>(this T? o) 
    where T : struct
{
        return o == null || o.Value.Equals(default(T));
}

如果它为空,调用肯定会失败(我曾经尝试在字符串上实现一个IsNullOrEmpty方法,当我意识到时感到很愚蠢),但对于值类型来说它会很好地工作。 - johnc
可空类型内置了一个名为 HasValue 的属性。 - Daniel
@johnc,如果o为null,调用不会失败。扩展方法实际上是静态方法,而不是实例方法。对我来说,IsNullOrEmpty扩展方法运行良好... - Thomas Levesque

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接