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

232
public static bool In<T>(this T source, params T[] list)
{
  if(null==source) throw new ArgumentNullException("source");
  return list.Contains(source);
}

使我能够替换:

if(reallyLongIntegerVariableName == 1 || 
    reallyLongIntegerVariableName == 6 || 
    reallyLongIntegerVariableName == 9 || 
    reallyLongIntegerVariableName == 11)
{
  // do something....
}

and

if(reallyLongStringVariableName == "string1" || 
    reallyLongStringVariableName == "string2" || 
    reallyLongStringVariableName == "string3")
{
  // do something....
}

and

if(reallyLongMethodParameterName == SomeEnum.Value1 || 
    reallyLongMethodParameterName == SomeEnum.Value2 || 
    reallyLongMethodParameterName == SomeEnum.Value3 || 
    reallyLongMethodParameterName == SomeEnum.Value4)
{
  // do something....
}

使用:

if(reallyLongIntegerVariableName.In(1,6,9,11))
{
      // do something....
}

and

if(reallyLongStringVariableName.In("string1","string2","string3"))
{
      // do something....
}

and

if(reallyLongMethodParameterName.In(SomeEnum.Value1, SomeEnum.Value2, SomeEnum.Value3, SomeEnum.Value4)
{
  // do something....
}

2
如果你使用了 System.Linq,那么它就可以编译。 - Ryu
11
“EqualsAnyOf”可能比“In”更合适作为名称? - Tom Bushell
10
我不确定我喜欢它 - 我喜欢“In”的简洁性,但也许“IsIn”会更好。 - Winston Smith
50
使用相同的Contains方法:(new[] { 1, 2, 3 }).Contains(a) - Max Toro
4
我也想到了In<T>(...),并且发现它是标准库之外最有用的扩展方法。但是我对In这个名称感到不太满意。方法名应该描述其功能,但是In并没有做到这一点。我将其命名为IsAnyOf<T>(...),但我觉得IsIn<T>(...)也可以。 - JBSnorro
显示剩余7条评论

160

我在我的MiscUtil项目中有各种扩展方法(完整源代码可在那里获得-我不会在此重复)。 我最喜欢的一些扩展方法包括其他类(如范围):

日期和时间相关的内容-主要用于单元测试。 不确定我会在生产中使用它们 :)

var birthday = 19.June(1976);
var workingDay = 7.Hours() + 30.Minutes();

区间和步长 - 感谢Marc Gravell提供的操作符内容,使这成为可能:

var evenNaturals = 2.To(int.MaxValue).Step(2);
var daysSinceBirth = birthday.To(DateTime.Today).Step(1.Days());

比较:

var myComparer = ProjectionComparer.Create(Person p => p.Name);
var next = myComparer.ThenBy(p => p.Age);
var reversed = myComparer.Reverse();

参数检查:

x.ThrowIfNull("x");

将 LINQ to XML 应用于匿名类型 (或其他具有适当属性的类型):

// <Name>Jon</Name><Age>32</Age>
new { Name="Jon", Age=32}.ToXElements();
// Name="Jon" Age="32" (as XAttributes, obviously)
new { Name="Jon", Age=32}.ToXAttributes()

推送LINQ - 在此解释需要太长时间,但可以搜索了解。


1
太好了!你应该把它放在Google Code或CodePlex上,这样我就可以给你发送一些补丁 :-) 我保证它会很易读 :-P - chakrit
3
你已经可以看到代码了。跟随第一句话中的链接 - 完整的源代码都在那里。 - Jon Skeet
1
@bovium:如果你不介意,我宁愿自己做,将其放在code.google.com上并管理项目。显然,只要你保持适当的归属权,你可以将其放在Codeplex上,但除非你非常需要,否则我宁愿很快自己解决 :) - Jon Skeet
1
仅仅因为我在那个库中做了许多其他的零碎工作。你可以为你的项目拷贝一份,但我也想保留一份在我的项目中。 - Jon Skeet
1
@lasseespeholt:在这两种情况下,你都必须在使用它们的每个源文件中定义minMinutes,这可能会很烦人。使用扩展方法只需要添加using指令(我们通常也不看),但这确实是一种品味问题。 - Jon Skeet
显示剩余8条评论

147

string.Format快捷方式:

public static class StringExtensions
{
    // Enable quick and more natural string.Format calls
    public static string F(this string s, params object[] args)
    {
        return string.Format(s, args);
    }
}

例子:

var s = "The co-ordinate is ({0}, {1})".F(point.X, point.Y);

快速复制粘贴请点击这里

你不认为在输入 "some string".F("param")string.Format("some string", "param")相比,前者更加自然吗?

为了更易读的名称,请尝试以下建议之一:

s = "Hello {0} world {1}!".Fmt("Stack", "Overflow");
s = "Hello {0} world {1}!".FormatBy("Stack", "Overflow");
s = "Hello {0} world {1}!".FormatWith("Stack", "Overflow");
s = "Hello {0} world {1}!".Display("Stack", "Overflow");
s = "Hello {0} world {1}!".With("Stack", "Overflow");

..


11
这确实很简短,但对于你团队的新成员来说可能难以阅读。 - Jon Skeet
3
我认为代码的可读性在整个代码中比许多简写语句更重要,这些简写语句可以很快查找或询问。 - chakrit
6
个人希望有一个单独的格式化对象,让BCL能够解析一次并重复使用该模式。这将提高可读性和性能。我已经向BCL团队提出了请求 - 让我们拭目以待... - Jon Skeet
3
这是一个扩展方法,当然对于新团队成员来说可能会很难读懂。我想这就是这些诙谐内容的意图吧?不然新成员怎么知道我们有多聪明呢? - MarkJ
17
好的,我会尽力进行翻译。你提供的内容是:“好的...我刚刚开始实践并选择了. With--所以你得到“This is a {0}”。".With("test") 这样书写非常易读而且通顺。”FYI。” - klkitchens
显示剩余15条评论

89

这些有用吗?

public static bool CoinToss(this Random rng)
{
    return rng.Next(2) == 0;
}

public static T OneOf<T>(this Random rng, params T[] things)
{
    return things[rng.Next(things.Length)];
}

Random rand;
bool luckyDay = rand.CoinToss();
string babyName = rand.OneOf("John", "George", "Radio XBR74 ROCKS!");

这个函数模仿了Python中的random.choice(seq)函数。不错。 - Daren Thomas
6
有几点建议:我建议 OneOf 应该接受 任何 IList<T>,这样你总是可以同时有一个重载版本,它使用 params 参数并将其传递到 IList<T> 重载中。我在底部给出了一个答案,其中包含一个类似于你的 CoinTossNextBool 方法,但它还带有一个重载版本,该版本带有一个 probability 参数(如果我想让某些事情发生75%的时间怎么办?)此外,只是一点小问题:你的示例代码将引发 NullReferenceException,因为 rand 从未初始化。 - Dan Tao
3
我非常喜欢这个建议,但我更喜欢使用 rng.NextDouble() < .5 来实现 CoinToss,因为内部实际上是通过 .NextDouble() 实现 .Next(int) 的,因此这样可以避免类型转换、乘法和比较操作。 - Lasse Espeholt

76
public static class ComparableExtensions
{
  public static bool Between<T>(this T actual, T lower, T upper) where T : IComparable<T>
  {
    return actual.CompareTo(lower) >= 0 && actual.CompareTo(upper) < 0;
  }
}

例子:

if (myNumber.Between(3,7))
{
  // ....
}

19
我喜欢这个,但我正在尝试决定是否将边界检查设置为最小值包含在内,最大值不包含在内。我想知道这是否会令人困惑。例如:5.Between(5,10) 返回true,但5.Between(1,5)返回false。甚至不确定一个Within方法是否有帮助。你有什么想法? - Steve Hiner
12
“IsBetween”这个名字会不会更合适呢?或者也许可以分别使用“IsBetweenInclusive”和“IsBetweenExclusive”。不过默认使用哪一个我不确定。 - fretje
2
@Steve:如果它是一个日期时间扩展,那就更有意义了。 - Joel Coehoorn
16
对我来说,“between” 暗示着: 当使用 Between(5,10) 时,返回 false,而使用 10.Between(5,10) 同样也返回 false。这对我来说感觉很自然。 - Alex Baranosky
3
在我看来,有多个人对什么是自然有不同的想法。因此,可能需要明确说明正在使用的是什么(即包容性还是排他性),因为这可能是一个非常容易出错的来源。 - David Miani
显示剩余8条评论

58
扩展方法:
public static void AddRange<T, S>(this ICollection<T> list, params S[] values)
    where S : T
{
    foreach (S value in values)
        list.Add(value);
}

该方法适用于所有类型,您可以将一系列项作为参数添加到列表中。
示例:
var list = new List<Int32>();
list.AddRange(5, 4, 8, 4, 2);

15
建议修改为 IList<T> 更好。 - user1228
21
请使用集合初始化器 => var list = new List<int>{5,4,8,4,2}; - Arnis Lapsa
为什么不在你的方法中直接调用List<T>.AddRange(IEnumerable<T> collection)呢? - Rauhotz
8
@Will:实际上,最好接受一个ICollection<T>;然后它也可以用于例如LinkedList<T>HashSet<T>之类的非索引集合,而不仅仅是索引集合。 - Dan Tao
2
编辑以在 .NET 4.0 之前允许协变。 - BlueRaja - Danny Pflughoeft
显示剩余6条评论

55

请务必将此内容放入CodePlex项目中。

将对象序列化/反序列化为XML:

/// <summary>Serializes an object of type T in to an xml string</summary>
/// <typeparam name="T">Any class type</typeparam>
/// <param name="obj">Object to serialize</param>
/// <returns>A string that represents Xml, empty otherwise</returns>
public static string XmlSerialize<T>(this T obj) where T : class, new()
{
    if (obj == null) throw new ArgumentNullException("obj");

    var serializer = new XmlSerializer(typeof(T));
    using (var writer = new StringWriter())
    {
        serializer.Serialize(writer, obj);
        return writer.ToString();
    }
}

/// <summary>Deserializes an xml string in to an object of Type T</summary>
/// <typeparam name="T">Any class type</typeparam>
/// <param name="xml">Xml as string to deserialize from</param>
/// <returns>A new object of type T is successful, null if failed</returns>
public static T XmlDeserialize<T>(this string xml) where T : class, new()
{
    if (xml == null) throw new ArgumentNullException("xml");

    var serializer = new XmlSerializer(typeof(T));
    using (var reader = new StringReader(xml))
    {
        try { return (T)serializer.Deserialize(reader); }
        catch { return null; } // Could not be deserialized to this type.
    }
}

8
我倾向于将第一个称为ToXml()(就像ToString()一样)。 - Jay Bazuzi
1
如果原帖作者有意这样写,那我向他道歉,但使用MemoryStreams和XmlReader/XmlWriter有些过度了。StringReader和StringWriter类非常适合这个操作。 - Portman
2
注意,这不是线程安全的。你应该确保同步访问静态序列化器字典。 - Yann Schwartz
2
@Yann,@T,如果您只是添加“线程静态”属性,那么每个线程将创建一个新的缓存,这样做会更容易。无需同步。 - Frank Krueger
1
@Jonathan C Dickinson:从这里的MSDN文档http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx看来,使用的构造函数(new XmlSerializer(type))没有内存泄漏问题。所以也许不需要缓存代码? - slolife
显示剩余8条评论

46

枚举类型的ForEach循环

public static class FrameworkExtensions
{
    // a map function
    public static void ForEach<T>(this IEnumerable<T> @enum, Action<T> mapFunction)
    {
        foreach (var item in @enum) mapFunction(item);
    }
}

朴素的例子:

var buttons = GetListOfButtons() as IEnumerable<Button>;

// click all buttons
buttons.ForEach(b => b.Click());
很棒的示例:

Cool example:

// no need to type the same assignment 3 times, just
// new[] up an array and use foreach + lambda
// everything is properly inferred by csc :-)
new { itemA, itemB, itemC }
    .ForEach(item => {
        item.Number = 1;
        item.Str = "Hello World!";
    });

注意:

这不同于Select,因为Select 期望您的函数返回某些内容以便转换成另一个列表。

ForEach仅允许您对每个项目执行一些操作,而不进行任何变换/数据操作。

我创建它是为了以更加函数化的方式编程,而且我很惊讶List拥有ForEach,而IEnumerable却没有。

将此放入codeplex项目中。


13
为什么LINQ的IEnumerable<T>扩展不包括ForEach方法的帖子:https://dev59.com/zXRC5IYBdhLWcg3wYP2h#318493 - Neil
13
在使用该方法之前,我建议您阅读这篇文章:http://blogs.msdn.com/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx。 - jpbochi
2
@jpbochi:这只是微软的空话。 - abatishchev
1
@abatishchev,你的评论只是对微软的偏见。它并不能使埃里克所写的任何话语失效。一个人的观点并不因为他/她所在的公司而变得有效或无效。 - jpbochi
1
顺便说一下,让我澄清一点。我并没有说你不应该使用这个 ForEach 扩展方法。我只是说在决定是否使用它之前,你应该考虑 Eric 提出的观点。我阅读了它并决定不使用它。你可以自由地在你的代码中做任何你想做的事情。 - jpbochi
显示剩余6条评论

43

我的转换扩展插件,可以让您实现以下功能:

int i = myString.To<int>();

这就是TheSoftwareJedi.com上发布的内容:

public static T To<T>(this IConvertible obj)
{
  return (T)Convert.ChangeType(obj, typeof(T));
}

public static T ToOrDefault<T>
             (this IConvertible obj)
{
    try
    {
        return To<T>(obj);
    }
    catch
    {
        return default(T);
    }
}

public static bool ToOrDefault<T>
                    (this IConvertible obj,
                     out T newObj)
{
    try
    {
        newObj = To<T>(obj); 
        return true;
    }
    catch
    {
        newObj = default(T); 
        return false;
    }
}

public static T ToOrOther<T>
                       (this IConvertible obj,
                       T other)
{
  try
  {
      return To<T>obj);
  }
  catch
  {
      return other;
  }
}

public static bool ToOrOther<T>
                         (this IConvertible obj,
                         out T newObj,
                         T other)
{
    try
    {
        newObj = To<T>(obj);
        return true;
    }
    catch
    {
        newObj = other;
        return false;
    }
}

public static T ToOrNull<T>
                      (this IConvertible obj)
                      where T : class
{
    try
    {
        return To<T>(obj);
    }
    catch
    {
        return null;
    }
}

public static bool ToOrNull<T>
                  (this IConvertible obj,
                  out T newObj)
                  where T : class
{
    try
    {
        newObj = To<T>(obj);
        return true;
    }
    catch
    {
        newObj = null;
        return false;
    }
}

当出现失败时,您可以请求默认值(调用空构造函数或数值类型的“0”),指定“default”值(我称其为“other”),或者请求null(其中T:class)。我还提供了静默异常模型和一个典型的TryParse模型,该模型返回一个指示所采取操作的bool值,并且out参数保存新值。因此,我们的代码可以执行以下操作:

int i = myString.To<int>();
string a = myInt.ToOrDefault<string>();
//note type inference
DateTime d = myString.ToOrOther(DateTime.MAX_VALUE);
double d;
//note type inference
bool didItGiveDefault = myString.ToOrDefault(out d);
string s = myDateTime.ToOrNull<string>();

我无法很干净地将可空类型整合到整个过程中。在放弃之前,我尝试了大约20分钟。


64
个人而言,我不喜欢使用try/catch来确定结果的代码。在我的看法中,try/catch应该用于预期逻辑之外发生的错误。嗯…… - Pure.Krome
如果我不想让你使用这段代码,我就不会发布它! :) - TheSoftwareJedi
最后终于看到了一些新鲜的东西,我很喜欢它。 :) - Arnis Lapsa
8
至少应该将“catch”子句更改为只捕获ChangeType()在无法“转换”引用时会引发的异常。我想您不希望任何OutOfMemoryException,ExecutionEngineException,ThreadAbortException或类似异常被视为转换错误。否则,这些异常将很难跟踪错误。 - Christian.K
2
我认为 ToOrNullToOrDefault 的行为完全相同(即,如果您在引用类型上调用 ToOrDefault 进行不成功的转换,则它将返回 null)。但更重要的是,对我来说它似乎有点多余,因为 var s = myObject as string 可以完成与 var s = myObject.ToOrNull<string>() 相同的操作-- 但是不需要潜在地捕获 InvalidCastException。我错过了什么吗? - Dan Tao

43

我有一个用于记录异常的扩展方法:

public static void Log(this Exception obj)
{
  //your logging logic here
}

它的使用方式如下:

try
{
    //Your stuff here
}
catch(Exception ex)
{
    ex.Log();
}

[抱歉发了两次帖子;第二个设计更好:-)]


2
可能应该这样写: public static void Log(this Exception obj){} - Chris S
我认为这对于BCL或第三方异常很有用,但是如果您自己定义异常类型,则可以在基本异常类中放置日志记录。这样,您就不必记得调用Log()了。 - si618

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