你最喜欢的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个回答

7

我刚刚浏览了这个帖子的前4页,惊讶的是我没有看到一种缩短检查 InvokeRequired 的方式:

using System;
using System.Windows.Forms;

/// <summary>
/// Extension methods acting on Control objects.
/// </summary>
internal static class ControlExtensionMethods
{
    /// <summary>
    /// Invokes the given action on the given control's UI thread, if invocation is needed.
    /// </summary>
    /// <param name="control">Control on whose UI thread to possibly invoke.</param>
    /// <param name="action">Action to be invoked on the given control.</param>
    public static void MaybeInvoke(this Control control, Action action)
    {
        if (control != null && control.InvokeRequired)
        {
            control.Invoke(action);
        }
        else
        {
            action();
        }
    }

    /// <summary>
    /// Maybe Invoke a Func that returns a value.
    /// </summary>
    /// <typeparam name="T">Return type of func.</typeparam>
    /// <param name="control">Control on which to maybe invoke.</param>
    /// <param name="func">Function returning a value, to invoke.</param>
    /// <returns>The result of the call to func.</returns>
    public static T MaybeInvoke<T>(this Control control, Func<T> func)
    {
        if (control != null && control.InvokeRequired)
        {
            return (T)(control.Invoke(func));
        }
        else
        {
            return func();
        }
    }
}

使用方法:

myForm.MaybeInvoke(() => this.Text = "Hello world");

// Sometimes the control might be null, but that's okay.
var dialogResult = this.Parent.MaybeInvoke(() => MessageBox.Show(this, "Yes or no?", "Choice", MessageBoxButtons.YesNo));

7

二分查找:

public static T BinarySearch<T, TKey>(this IList<T> list, Func<T, TKey> keySelector, TKey key)
        where TKey : IComparable<TKey>
{
    int min = 0;
    int max = list.Count;
    int index = 0;
    while (min < max)
    {
        int mid = (max + min) / 2;
        T midItem = list[mid];
        TKey midKey = keySelector(midItem);
        int comp = midKey.CompareTo(key);
        if (comp < 0)
        {
            min = mid + 1;
        }
        else if (comp > 0)
        {
            max = mid - 1;
        }
        else
        {
            return midItem;
        }
    }
    if (min == max &&
        keySelector(list[min]).CompareTo(key) == 0)
    {
        return list[min];
    }
    throw new InvalidOperationException("Item not found");
}

使用方法(假设列表已按Id排序):

var item = list.BinarySearch(i => i.Id, 42);

当没有匹配项时,Enumerable.First抛出InvalidOperationException异常,这可能看起来很奇怪。

7
有时在列表中选定元素时,将字符串写出来并带有自定义分隔符很方便。
例如,如果您有一个List<Person> 并且想要循环输出用逗号分隔的姓氏,则可以这样做。
string result = string.Empty;
foreach (var person in personList) {
   result += person.LastName + ", ";
}
result = result.Substring(0, result.Length - 2);
return result;

或者您可以使用这个方便的扩展方法

public static string Join<T>(this IEnumerable<T> collection, Func<T, string> func, string separator)
{
  return String.Join(separator, collection.Select(func).ToArray());
}

并且像这样使用它

personList.Join(x => x.LastName, ", ");

在这种情况下,它会生成一个由逗号分隔的姓氏列表,产生相同的结果。

1
我将这个方法命名为ToDelimitedString以避免与内置的LINQ Join方法产生混淆。 - Joel Mueller

7

一些日期函数:

public static bool IsFuture(this DateTime date, DateTime from)
{
    return date.Date > from.Date;
}

public static bool IsFuture(this DateTime date)
{
    return date.IsFuture(DateTime.Now);
}

public static bool IsPast(this DateTime date, DateTime from)
{
    return date.Date < from.Date;
}

public static bool IsPast(this DateTime date)
{
    return date.IsPast(DateTime.Now);
}

1
我们的代码库中有一些类似的函数:IsBefore()、IsOnOrBefore()、IsOnOrAfter()、IsAfter()、IsBeforeToday()、IsAfterToday()。它们封装了一些相对简单的代码,但是它们显著提高了可读性。 - KeithS

7

不胜感激。以下是我主要关注的几个方面。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace Insert.Your.Namespace.Here.Helpers
{
    public static class Extensions
    {
        public static bool IsNullOrEmpty<T>(this IEnumerable<T> iEnumerable)
        {
            // Cheers to Joel Mueller for the bugfix. Was .Count(), now it's .Any()
            return iEnumerable == null ||
                   !iEnumerable.Any();
        }

        public static IList<T> ToListIfNotNullOrEmpty<T>(this IList<T> iList)
        {
            return iList.IsNullOrEmpty() ? null : iList;
        }

        public static PagedList<T> ToPagedListIfNotNullOrEmpty<T>(this PagedList<T> pagedList)
        {
            return pagedList.IsNullOrEmpty() ? null : pagedList;
        }

        public static string ToPluralString(this int value)
        {
            return value == 1 ? string.Empty : "s";
        }

        public static string ToReadableTime(this DateTime value)
        {
            TimeSpan span = DateTime.Now.Subtract(value);
            const string plural = "s";


            if (span.Days > 7)
            {
                return value.ToShortDateString();
            }

            switch (span.Days)
            {
                case 0:
                    switch (span.Hours)
                    {
                        case 0:
                            if (span.Minutes == 0)
                            {
                                return span.Seconds <= 0
                                           ? "now"
                                           : string.Format("{0} second{1} ago",
                                                           span.Seconds,
                                                           span.Seconds != 1 ? plural : string.Empty);
                            }
                            return string.Format("{0} minute{1} ago",
                                                 span.Minutes,
                                                 span.Minutes != 1 ? plural : string.Empty);
                        default:
                            return string.Format("{0} hour{1} ago",
                                                 span.Hours,
                                                 span.Hours != 1 ? plural : string.Empty);
                    }
                default:
                    return string.Format("{0} day{1} ago",
                                         span.Days,
                                         span.Days != 1 ? plural : string.Empty);
            }
        }

        public static string ToShortGuidString(this Guid value)
        {
            return Convert.ToBase64String(value.ToByteArray())
                .Replace("/", "_")
                .Replace("+", "-")
                .Substring(0, 22);
        }

        public static Guid FromShortGuidString(this string value)
        {
            return new Guid(Convert.FromBase64String(value.Replace("_", "/")
                                                         .Replace("-", "+") + "=="));
        }

        public static string ToStringMaximumLength(this string value, int maximumLength)
        {
            return ToStringMaximumLength(value, maximumLength, "...");
        }

        public static string ToStringMaximumLength(this string value, int maximumLength, string postFixText)
        {
            if (string.IsNullOrEmpty(postFixText))
            {
                throw new ArgumentNullException("postFixText");
            }

            return value.Length > maximumLength
                       ? string.Format(CultureInfo.InvariantCulture,
                                       "{0}{1}",
                                       value.Substring(0, maximumLength - postFixText.Length),
                                       postFixText)
                       :
                           value;
        }

        public static string SlugDecode(this string value)
        {
            return value.Replace("_", " ");
        }

        public static string SlugEncode(this string value)
        {
            return value.Replace(" ", "_");
        }
    }
}

4
在IsNullOrEmpty这个方法中,我不想在一个有一百万项的枚举器上调用它。这会循环遍历所有一百万项,只是为了告诉我它不是空的。更好的做法是:返回 iEnumerable == null || !iEnumerable.Any(); - Joel Mueller
哦,伙计 - 太棒了!我从来不知道那个!多谢你啊,伙计。(我的上面的帖子,已编辑。) - Pure.Krome
ToPluralString() 这个函数太过简单化了。作为非英语母语者,我觉得它很傻,但即便在英语中也并不是很好用。;-) - peSHIr
@peSHIr:能否举个例子?基本上,对于大量(让我们保持简单,说“大多数”)的“事物”,它们要么是0,要么是2+..以s结尾来定义它们是复数(或者对于0不用加)。例如,猫/猫。狗/狗。朋友/朋友们。臭虫/臭虫。现在,这对于像仙女/仙女(以及所有以y结尾的其他单词)或牙齿/牙齿等事物是行不通的。所以这是一个帮手...但不是将单数变为复数修改的全部和终极方法。 - Pure.Krome
@Pure.Krome:你已经举了一些例子。;-) 还有一些:index/indices、saldo/saldi和其他基于拉丁语的词汇。如果这只是ToReadableTime()中使用的辅助程序——即使该函数没有任何全球化,它也可以很好地工作——但是该函数没有被使用(?),我希望ToPluralString()是私有的,而不是公共的。 - peSHIr
显示剩余2条评论

7
public static class EnumerableExtensions
{
    [Pure]
    public static U MapReduce<T, U>(this IEnumerable<T> enumerable, Func<T, U> map, Func<U, U, U> reduce)
    {
        CodeContract.RequiresAlways(enumerable != null);
        CodeContract.RequiresAlways(enumerable.Skip(1).Any());
        CodeContract.RequiresAlways(map != null);
        CodeContract.RequiresAlways(reduce != null);
        return enumerable.AsParallel().Select(map).Aggregate(reduce);
    }
    [Pure]
    public static U MapReduce<T, U>(this IList<T> list, Func<T, U> map, Func<U, U, U> reduce)
    {
        CodeContract.RequiresAlways(list != null);
        CodeContract.RequiresAlways(list.Count >= 2);
        CodeContract.RequiresAlways(map != null);
        CodeContract.RequiresAlways(reduce != null);
        U result = map(list[0]);
        for (int i = 1; i < list.Count; i++)
        {
            result = reduce(result,map(list[i]));
        }
        return result;
    }

    //Parallel version; creates garbage
    [Pure]
    public static U MapReduce<T, U>(this IList<T> list, Func<T, U> map, Func<U, U, U> reduce)
    {
        CodeContract.RequiresAlways(list != null);
        CodeContract.RequiresAlways(list.Skip(1).Any());
        CodeContract.RequiresAlways(map != null);
        CodeContract.RequiresAlways(reduce != null);

        U[] mapped = new U[list.Count];
        Parallel.For(0, mapped.Length, i =>
            {
                mapped[i] = map(list[i]);
            });
        U result = mapped[0];
        for (int i = 1; i < list.Count; i++)
        {
            result = reduce(result, mapped[i]);
        }
        return result;
    }

}

使用鲜为人知的.NET 4.0设计契约API,可以获得额外的加分。 - Judah Gabriel Himango
3
在契约检查中,调用 "Count" 枚举可枚举对象是否会带来一定风险?或者这不是运行时检查? - flq
这仍然是危险的,因为某些可枚举对象只能被迭代一次,但我已经修复了它,使其至少在两次迭代后停止而不是确定整个计数。 - Joel Coehoorn

6
我发现自己一遍又一遍地这样做...
public static bool EqualsIgnoreCase(this string a, string b)
{
    return string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
}

...接着是 StartsWithIgnoreCaseEndsWithIgnoreCaseContainsIgnoreCase

6

当使用键为字符串的字典时,使用不区分大小写的搜索返回现有键。 我们的用例是用于文件路径。

/// <summary>
/// Gets the key using <paramref name="caseInsensitiveKey"/> from <paramref name="dictionary"/>.
/// </summary>
/// <typeparam name="T">The dictionary value.</typeparam>
/// <param name="dictionary">The dictionary.</param>
/// <param name="caseInsensitiveKey">The case insensitive key.</param>
/// <returns>
/// An existing key; or <see cref="string.Empty"/> if not found.
/// </returns>
public static string GetKeyIgnoringCase<T>(this IDictionary<string, T> dictionary, string caseInsensitiveKey)
{
    if (string.IsNullOrEmpty(caseInsensitiveKey)) return string.Empty;
    foreach (string key in dictionary.Keys)
    {
        if (key.Equals(caseInsensitiveKey, StringComparison.InvariantCultureIgnoreCase))
        {
            return key;
        }
    }
    return string.Empty;
}

2
字典中有一个独立的键集合属性,可能能更快地完成这个任务。 - Joel Coehoorn
2
如果您需要大小写不敏感的键,可以将StringComparer.InvariantIgnoreCase传递给字典构造函数。 - Thomas Levesque
@Thomas - 更好了!假设您可以访问ctor,但绝对是最佳方法。 - si618

6

通过操作系统文件系统信息进行文件/目录比较的函数。这对于比较共享文件和本地文件非常有用。

用法:

DirectoryInfo dir = new DirectoryInfo(@"C:\test\myShareDir");
Console.WriteLine(dir.IsSameFileAs(@"\\myMachineName\myShareDir"));

FileInfo file = new FileInfo(@"C:\test\myShareDir\file.txt");
Console.WriteLine(file.IsSameFileAs(@"\\myMachineName\myShareDir\file.txt"));

代码:

public static class FileExtensions
{
    struct BY_HANDLE_FILE_INFORMATION
    {
        public uint FileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
        public uint VolumeSerialNumber;
        public uint FileSizeHigh;
        public uint FileSizeLow;
        public uint NumberOfLinks;
        public uint FileIndexHigh;
        public uint FileIndexLow;
    }

    //
    // CreateFile constants
    //
    const uint FILE_SHARE_READ = 0x00000001;
    const uint OPEN_EXISTING = 3;
    const uint GENERIC_READ = (0x80000000);
    const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;


    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr CreateFile(
        string lpFileName,
        uint dwDesiredAccess,
        uint dwShareMode,
        IntPtr lpSecurityAttributes,
        uint dwCreationDisposition,
        uint dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool GetFileInformationByHandle(IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation);

    public static bool IsSameFileAs(this FileSystemInfo file, string path)
    {
        BY_HANDLE_FILE_INFORMATION fileInfo1, fileInfo2;
        IntPtr ptr1 = CreateFile(file.FullName, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
        if ((int)ptr1 == -1)
        {
            System.ComponentModel.Win32Exception e = new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            throw e;
        }
        IntPtr ptr2 = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
        if ((int)ptr2 == -1)
        {
            System.ComponentModel.Win32Exception e = new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            throw e;
        }
        GetFileInformationByHandle(ptr1, out fileInfo1);
        GetFileInformationByHandle(ptr2, out fileInfo2);

        return ((fileInfo1.FileIndexHigh == fileInfo2.FileIndexHigh) &&
            (fileInfo1.FileIndexLow == fileInfo2.FileIndexLow));
    }
}

2
这不是使用扩展方法,只是一个静态类。 - bovium
1
将以下静态方法转换为扩展方法: static public bool CompareFiles(string path1, string path2) 变为 static public bool IsSameFileAs(this string path1, string path2); 然后使用方式如下: if (file1.IsSameFileAs(file2)) - Armstrongest
1
不同驱动器上的两个不同文件可能会巧合地具有相同的FileIndex。您需要比较VolumeSerialNumber,但是您的示例将失败,因为VolumeSerialNumbers是不同的。 - Rasmus Faber
请参见https://dev59.com/f3RC5IYBdhLWcg3wFdJx。 - Rasmus Faber
4
你应该关闭那些文件句柄了吧? - marijne

6

字典的Pythonic方法:

/// <summary>
/// If a key exists in a dictionary, return its value, 
/// otherwise return the default value for that type.
/// </summary>
public static U GetWithDefault<T, U>(this Dictionary<T, U> dict, T key)
{
    return dict.GetWithDefault(key, default(U));
}

/// <summary>
/// If a key exists in a dictionary, return its value,
/// otherwise return the provided default value.
/// </summary>
public static U GetWithDefault<T, U>(this Dictionary<T, U> dict, T key, U defaultValue)
{
    return dict.ContainsKey(key)
        ? dict[key]
        : defaultValue;
}

当您想要在文件名中添加时间戳以确保唯一性时,这将非常有用。

/// <summary>
/// Format a DateTime as a string that contains no characters
//// that are banned from filenames, such as ':'.
/// </summary>
/// <returns>YYYY-MM-DD_HH.MM.SS</returns>
public static string ToFilenameString(this DateTime dt)
{
    return dt.ToString("s").Replace(":", ".").Replace('T', '_');
}

1
直接使用 dt.ToString("yyy-MM-dd_HH.mm.ss"); 可以避免创建额外的 2 个字符串实例。由于此格式不包含时区组件,建议使用协调世界时(UTC)时间,可以通过 dt.ToUniversalTime().ToString(...) 实现。 - devstuff
4
最好使用TryGetValue,这样可以减少查找次数,而不是进行两次查找。 - Anton Tykhyy

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