您所看到的扩展方法的最佳或最有趣的用法是什么?[关闭]


71

我开始真的很喜欢扩展方法...我想知道她是不是偶然发现了一个真正让他们震惊或只是聪明的人。

我今天写的一个例子:

根据其他用户的评论进行了编辑:

public static IEnumerable<int> To(this int fromNumber, int toNumber) {
    while (fromNumber < toNumber) {
        yield return fromNumber;
        fromNumber++;
    }
}

这允许将for循环编写为foreach循环:

foreach (int x in 0.To(16)) {
    Console.WriteLine(Math.Pow(2, x).ToString());
}

我等不及要看其他示例了!请享用!


19
您的方法主要是重新实现Enumerable.Range(msdn.microsoft.com/en-us/library/…)。不同之处在于Range范围是起点和计数,而您的范围是from和a。您还通过包含高端(<=)来违反常规的边界实践(<)。最后,它可以倒退,但是在实践中几乎没有必要。
马修·弗拉申

8
违背正常边界实践?废话。语言和概念始终包含“ 0到16”。在for循环中,通常将max + 1用作条件中的数字,仅是因为5项列表中的索引为0 ... 4,所以看“ <5”比“ <<更有意义。 = 4“。
桑德


6
我认为for(int x=0; x<=16; ++x)对经验丰富的程序员更容易理解。但是,近距离范围很少见。
汤姆·哈汀

3
像这样的问题使我想写更多的C#...
Daniel Huckstep 09年

Answers:


18

完整的解决方案太大了,不能放在这里,但是我写了一系列扩展方法,使您可以轻松地将DataTable转换为CSV。

public static String ToCSV(this DataTable dataTable)
{
    return dataTable.ToCSV(null, COMMA, true);
}  

public static String ToCSV(this DataTable dataTable, String qualifier)
{
    return dataTable.ToCSV(qualifier, COMMA, true);
}

private static String ToCSV(this DataTable dataTable, String qualifier, String delimiter, Boolean includeColumnNames)
{
    if (dataTable == null) return null;

    if (qualifier == delimiter)
    {
        throw new InvalidOperationException(
            "The qualifier and the delimiter are identical. This will cause the CSV to have collisions that might result in data being parsed incorrectly by another program.");
    }

    var sbCSV = new StringBuilder();

    var delimiterToUse = delimiter ?? COMMA;

    if (includeColumnNames) 
        sbCSV.AppendLine(dataTable.Columns.GetHeaderLine(qualifier, delimiterToUse));

    foreach (DataRow row in dataTable.Rows)
    {
        sbCSV.AppendLine(row.ToCSVLine(qualifier, delimiterToUse));
    }

    return sbCSV.Length > 0 ? sbCSV.ToString() : null;
}

private static String ToCSVLine(this DataRow dataRow, String qualifier, String delimiter)
{
    var colCount = dataRow.Table.Columns.Count;
    var rowValues = new String[colCount];

    for (var i = 0; i < colCount; i++)
    {
        rowValues[i] = dataRow[i].Qualify(qualifier);
    }

    return String.Join(delimiter, rowValues);
}

private static String GetHeaderLine(this DataColumnCollection columns, String qualifier, String delimiter)
{
    var colCount = columns.Count;
    var colNames = new String[colCount];

    for (var i = 0; i < colCount; i++)
    {
        colNames[i] = columns[i].ColumnName.Qualify(qualifier);
    }

    return String.Join(delimiter, colNames);
}

private static String Qualify(this Object target, String qualifier)
{
    return qualifier + target + qualifier;
}

在一天结束时,您可以这样称呼它:

someDataTable.ToCSV(); //Plain old CSV
someDataTable.ToCSV("\""); //Double quote qualifier
someDataTable.ToCSV("\"", "\t"); //Tab delimited

我实际上写了一整套其他的扩展方法作为重载,这些重载方法将允许您传入一个自定义类,该类定义了诸如格式,列名等内容。
乔什(Josh)

18

这是最近一直在我心中扮演的角色:

public static IDisposable Tag(this HtmlHelper html, string tagName)
{
    if (html == null)
        throw new ArgumentNullException("html");

    Action<string> a = tag => html.Write(String.Format(tag, tagName));
    a("<{0}>");
    return new Memento(() => a("</{0}>"));
}

像这样使用:

using (Html.Tag("ul"))
{
    this.Model.ForEach(item => using(Html.Tag("li")) Html.Write(item));
    using(Html.Tag("li")) Html.Write("new");
}

备忘录是一门方便的课程:

public sealed class Memento : IDisposable
{
    private bool Disposed { get; set; }
    private Action Action { get; set; }

    public Memento(Action action)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        Action = action;
    }

    void IDisposable.Dispose()
    {
        if (Disposed)
            throw new ObjectDisposedException("Memento");

        Disposed = true;
        Action();
    }
}

并完成依赖关系:

public static void Write(this HtmlHelper html, string content)
{
    if (html == null)
        throw new ArgumentNullException("html");

    html.ViewContext.HttpContext.Response.Write(content);
}

太好了!(不知道为什么有人拒绝了它)也许这个例子对于那些还没有进入ASP.NET MVC的人来说没有意义:)
DSO

不错的模式在这里继续。谢谢。+1
德鲁·诺阿克斯

非常酷,也感谢我为扩展程序提供的帮助+1
rmoore

11
我喜欢这个想法,但是我不确定为什么Memento会从其Dispose方法中引发异常。MSDN表示:“为确保始终正确清理资源,Dispose方法应可多次调用而不会引发异常。” bit.ly/NV3AH
乔尔·穆勒

1
对于资源以外的其他东西使用处置模式是一个经常争论的问题,双方都没有明确的赢家。话虽如此,引发异常仍然不是一个好主意,因为您实际上无法控制Dispose方法将被调用多少次。想象一下,如果有人在using语句内调用Memento并显式调用Dispose,会发生什么情况。最后,您有两次对Dispose的调用-一个由用户显式调用,另一个在编译器扩展using语句时添加。更好的选择可能是使用Assert。
Scott Dorman

13

我不喜欢INotifyPropertyChanged要求属性名称作为字符串传递的接口。我想要一种强类型的方式来在编译时检查是否仅针对存在的属性引发和处理属性更改。我使用以下代码来做到这一点:

public static class INotifyPropertyChangedExtensions
{
    public static string ToPropertyName<T>(this Expression<Func<T>> @this)
    {
        var @return = string.Empty;
        if (@this != null)
        {
            var memberExpression = @this.Body as MemberExpression;
            if (memberExpression != null)
            {
                @return = memberExpression.Member.Name;
            }
        }
        return @return;
    }
}

在实现的类中,INotifyPropertyChanged我包括以下辅助方法:

protected void NotifySetProperty<T>(ref T field, T value,
    Expression<Func<T>> propertyExpression)
{
    if (field == null ? value != null : !field.Equals(value))
    {
        field = value;
        this.NotifyPropertyChanged(propertyExpression.ToPropertyName());
    }
}

这样最后我可以做这种事情:

private string _name;
public string Name
{
    get { return _name; }
    set { this.NotifySetProperty(ref _name, value, () => this.Name); }
}

它是强类型的,我仅引发实际上会更改其值的属性的事件。


我使用像这样的变量private static readonly PropertyChangedEventArgs _namePropertyChangedEventArgs = new PropertyChangedEventArgs("Name");,并将其传递给OnPropertyChanged。跟踪字符串比查找字符串要容易得多(查找所有引用),并且在内存上便宜得多(每种类型仅创建一次,而不是每个实例每次更改一次)。
山姆·哈威尔

嗨,280Z28,可能有多种技术组合会很有用,因为您的方法仍然具有需要更新的字符串文字。在实践中,我发现我使用的方法很好,特别是因为属性更改通常是用户生成的,因此速度较慢且不常见。我将必须检查以确保不会造成内存泄漏。谢谢。

12

嗯,这并不是很聪明,但是我修改了---- OrDefault方法,因此您可以内联指定默认项,而不是稍后在代码中检查null:

    public static T SingleOrDefault<T> ( this IEnumerable<T> source, 
                                    Func<T, bool> action, T theDefault )
    {
        T item = source.SingleOrDefault<T>(action);

        if (item != null)
            return item;

        return theDefault;
    }

它令人难以置信的简单,但确实有助于清理这些空检查。最适合在用户界面需要X项列表(例如锦标赛系统或玩家插槽)并且要显示“空座位”的情况下使用。

用法:

    return jediList.SingleOrDefault( 
                 j => j.LightsaberColor == "Orange", 
               new Jedi() { LightsaberColor = "Orange", Name = "DarthNobody");

很好,我一直想知道为什么在SingleOrDefault和FirstOrDefault中不包含该重载,所以很好地主动添加了它!
rmoore

我做了类似的事情,尽管我使用Func <T>代替了T作为默认值。
Svish

3
请注意,这对于非可空类型将无法正常工作。找不到任何项目时,内置的SingleOrDefault扩展将返回default(T),并且对于引用类型或可为空的值类型,该扩展名将仅为null。
路加福音

例如,无论“ theDefault”参数的值如何,当T为int且找不到匹配项时,您的方法将始终返回0。
路加福音

5
我个人认为它比.SingleOrDefault()?? new Foo()
Johannes Rudolph 2010年

11

这是我一起砍的东西,请随时挖洞。它接受一个(有序的)整数列表,并返回一个连续范围的字符串列表。例如:

1,2,3,7,10,11,12  -->  "1-3","7","10-12"

函数(在静态类中):

public static IEnumerable<string> IntRanges(this IEnumerable<int> numbers)
{
    int rangeStart = 0;
    int previous = 0;

    if (!numbers.Any())
        yield break;

    rangeStart = previous = numbers.FirstOrDefault();

    foreach (int n in numbers.Skip(1))
    {
        if (n - previous > 1) // sequence break - yield a sequence
        {
            if (previous > rangeStart)
            {
                yield return string.Format("{0}-{1}", rangeStart, previous);
            }
            else
            {
                yield return rangeStart.ToString();
            }
            rangeStart = n;
        }
        previous = n;
    }

    if (previous > rangeStart)
    {
        yield return string.Format("{0}-{1}", rangeStart, previous);
    }
    else
    {
        yield return rangeStart.ToString();
    }
}

用法示例:

this.WeekDescription = string.Join(",", from.WeekPattern.WeekPatternToInts().IntRanges().ToArray());

此代码用于转换来自需要DailyWTF的时间表应用程序的数据。WeekPattern是存储在字符串“ 0011011100 ...”中的位掩码。WeekPatternToInts()将其转换为IEnumerable <int>,在这种情况下为[3,4,6,7,8],该值变为“ 3-4,6-8”。它为用户提供了关于授课的学术周范围的紧凑描述。


我想不出可以立即使用它了,但还是很棒的:)
乔什(Josh)2009年

谢谢!我添加了一个示例,说明了如何使用它。
geofftnz

为什么要执行不必要的操作,例如不必要的分配previous = n,在每次迭代中首先检查并格式化单个数字?
okutane

修复了德米特里(Dmitriy)指出的问题(谢谢!)
geofftnz

1
你也有一个反向?
Svish

11

我喜欢使用的两个是我编写的InsertWhere <T>和RemoveWhere <T>扩展方法。在WPF和Silverlight中使用ObservableCollections,我经常需要修改有序列表而不重新创建它们。这些方法允许我根据提供的Func进行插入和删除,因此.OrderBy()不需要重新调用。

    /// <summary>
    /// Removes all items from the provided <paramref name="list"/> that match the<paramref name="predicate"/> expression.
    /// </summary>
    /// <typeparam name="T">The class type of the list items.</typeparam>
    /// <param name="list">The list to remove items from.</param>
    /// <param name="predicate">The predicate expression to test against.</param>
    public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> predicate)
    {
        T[] copy = new T[] { };
        Array.Resize(ref copy, list.Count);
        list.CopyTo(copy, 0);

        for (int i = copy.Length - 1; i >= 0; i--)
        {
            if (predicate(copy[i]))
            {
                list.RemoveAt(i);
            }
        }
    }

    /// <summary>
    /// Inserts an Item into a list at the first place that the <paramref name="predicate"/> expression fails.  If it is true in all cases, then the item is appended to the end of the list.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    /// <param name="obj"></param>
    /// <param name="predicate">The sepcified function that determines when the <paramref name="obj"/> should be added. </param>
    public static void InsertWhere<T>(this IList<T> list, T obj, Func<T, bool> predicate)
    {
        for (int i = 0; i < list.Count; i++)
        { 
            // When the function first fails it inserts the obj paramiter. 
            // For example, in a list myList of ordered Int32's {1,2,3,4,5,10,12}
            // Calling myList.InsertWhere( 8, x => 8 > x) inserts 8 once the list item becomes greater then or equal to it.
            if(!predicate(list[i]))
            {
                list.Insert(i, obj);
                return;
            }
        }

        list.Add(obj);
    }

编辑:
Talljoe对我匆忙构建的RemoveWhere / RemoveAll进行了一些重大改进。通过〜3mill项目每三分之一删除一次,新版本仅花费〜50毫秒(如果可以调用List.RemoveAll!,则只需不到10毫秒),而不是RemoveWhere的几秒钟(我厌倦了等待它)。

这是他的大幅改进版本,再次感谢!

    public static void RemoveAll<T>(this IList<T> instance, Predicate<T> predicate)
    {
        if (instance == null)
            throw new ArgumentNullException("instance");
        if (predicate == null)
            throw new ArgumentNullException("predicate");
        if (instance is T[])
            throw new NotSupportedException();

        var list = instance as List<T>;
        if (list != null)
        {
            list.RemoveAll(predicate);
            return;
        }

        int writeIndex = 0;
        for (int readIndex = 0; readIndex < instance.Count; readIndex++)
        {
            var item = instance[readIndex];
            if (predicate(item)) continue;

            if (readIndex != writeIndex)
            {
                instance[writeIndex] = item;
            }
            ++writeIndex;
        }

        if (writeIndex != instance.Count)
        {
            for (int deleteIndex = instance.Count - 1; deleteIndex >= writeIndex; --deleteIndex)
            {
                instance.RemoveAt(deleteIndex);
            }
        }
    }

3
由于删除项目时所有内存转移,RemoveWhere效率低下(除非IList是LinkedList)。我在这里创建了一个修改过的版本:pastebin.com/f20e73b4e差异:1)重命名为“ RemoveAll”以匹配List <T>的版本。2)调用List <T>的版本(如果适用)(甚至比我的版本3更为有效)使用两个索引来列出并进行值的就地覆盖。4)处理有人通过数组的情况(我想我实际上更喜欢抛出异常,但是我想在修改数组之前做到这一点-供读者练习)。
Talljoe

我什至从未注意到List <T> .RemoveAll,我只是假设所有扩展都在扩展IList <T>,而后者没有它。感谢您指出!可悲的是我无法使用它,因为ObservableCollection只是继承自IList。需要注意的一点是,对点2的检查将导致堆栈溢出,Func <T,bool>到Predicate <T>不能转换为Predicate。不过,您的后半部分要比我的快得多,我肯定会实现它。如果您不介意,我将使用更新的版本来编辑我的帖子。
rmoore

快走 关于无限递归有趣的是,我很确定自己已经对其进行了测试并且能够正常工作。 耸耸肩 这将使其工作:list.RemoveAll(t => predicate(t));
2009年

10

我有各种.Debugify扩展方法,这些方法对于将对象转储到日志文件很有用。例如,这是我的Dictionary调试(我具有List,Datatable,param数组等):

public static string Debugify<TKey, TValue>(this Dictionary<TKey, TValue> dictionary) {
    string Result = "";

    if (dictionary.Count > 0) {
        StringBuilder ResultBuilder = new StringBuilder();

        int Counter = 0;
        foreach (KeyValuePair<TKey, TValue> Entry in dictionary) {
            Counter++;
            ResultBuilder.AppendFormat("{0}: {1}, ", Entry.Key, Entry.Value);
            if (Counter % 10 == 0) ResultBuilder.AppendLine();
        }
        Result = ResultBuilder.ToString();
    }
    return Result;
}

这是DbParameterCollection的一个(用于将数据库调用转储到日志文件中):

public static string Debugify(this DbParameterCollection parameters) {
    List<string> ParameterValuesList = new List<string>();

    foreach (DbParameter Parameter in parameters) {
        string ParameterName, ParameterValue;
        ParameterName = Parameter.ParameterName;

        if (Parameter.Direction == ParameterDirection.ReturnValue)
            continue;

        if (Parameter.Value == null || Parameter.Value.Equals(DBNull.Value))
            ParameterValue = "NULL";
        else
        {
            switch (Parameter.DbType)
            {
                case DbType.String:
                case DbType.Date:
                case DbType.DateTime:
                case DbType.Guid:
                case DbType.Xml:
                    ParameterValue
                        = "'" + Parameter
                                .Value
                                .ToString()
                                .Replace(Environment.NewLine, "")
                                .Left(80, "...") + "'"; // Left... is another nice one
                    break;

                default:
                    ParameterValue = Parameter.Value.ToString();
                    break;
            }

            if (Parameter.Direction != ParameterDirection.Input)
                ParameterValue += " " + Parameter.Direction.ToString();
        }

        ParameterValuesList.Add(string.Format("{0}={1}", ParameterName, ParameterValue));
    }

    return string.Join(", ", ParameterValuesList.ToArray());
}

结果示例:

Log.DebugFormat("EXEC {0} {1}", procName, params.Debugify);
// EXEC spProcedure @intID=5, @nvName='Michael Haren', @intRefID=11 OUTPUT

请注意,如果您数据库调用之后调用此方法,则也将填写输出参数。我在包含SP名称的行上调用此名称,因此可以将呼叫复制/粘贴到SSMS中进行调试。


这些使我的日志文件漂亮且易于生成,而不会中断我的代码。


9

一对将base-36字符串(!)转换为整数的扩展方法:

public static int ToBase10(this string base36)
{
    if (string.IsNullOrEmpty(base36))
        return 0;
    int value = 0;
    foreach (var c in base36.Trim())
    {
        value = value * 36 + c.ToBase10();
    }
    return value;
}

public static int ToBase10(this char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    c = char.ToUpper(c);
    if (c >= 'A' && c <= 'Z')
        return c - 'A' + 10;
    return 0;
}

(一些天才认为,将数字存储在数据库中的最佳方法是将它们编码为字符串。十进制占用太多空间。十六进制更好,但不使用字符GZ。因此,显然您将base-16扩展为base-36! )


2
嗯,需要有一个导入函数以您的示例为例,并将其直接发布到每日wtf上。(尽管最近我咨询了正在其数据库中创建和使用自定义guid的客户端。创建guid,切断末尾并附加时间戳。认真地wtf。)
rmoore

我使用了以36为基数的数字,但是仅当我需要支持大范围的人类可读标识符的地址时,这些标识符才需要适合于古老(且不可修改)系统的消息格式的4字节字段。我仍然认为这是一个合理的用例,尽管真正的解决方案是丢弃古老的系统。
罗伯特·罗斯尼

总有base64 :)我不确定我正在处理的应用程序中的决策过程是什么。有人告诉我,该应用程序实际上使用平面文件来存储数据,并且要使其成为多用户系统,他们只需在保存时将所有内容爆炸到数据库中,并在初始化时刷新本地文件存储即可。
geofftnz

7

我编写了一系列扩展方法,以使其更易于操作ADO.NET对象和方法:

在一条指令中从DbConnection创建DbCommand:

    public static DbCommand CreateCommand(this DbConnection connection, string commandText)
    {
        DbCommand command = connection.CreateCommand();
        command.CommandText = commandText;
        return command;
    }

向DbCommand添加参数:

    public static DbParameter AddParameter(this DbCommand command, string name, DbType dbType)
    {
        DbParameter p = AddParameter(command, name, dbType, 0, ParameterDirection.Input);
        return p;
    }

    public static DbParameter AddParameter(this DbCommand command, string name, DbType dbType, object value)
    {
        DbParameter p = AddParameter(command, name, dbType, 0, ParameterDirection.Input);
        p.Value = value;
        return p;
    }

    public static DbParameter AddParameter(this DbCommand command, string name, DbType dbType, int size)
    {
        return AddParameter(command, name, dbType, size, ParameterDirection.Input);
    }

    public static DbParameter AddParameter(this DbCommand command, string name, DbType dbType, int size, ParameterDirection direction)
    {
        DbParameter parameter = command.CreateParameter();
        parameter.ParameterName = name;
        parameter.DbType = dbType;
        parameter.Direction = direction;
        parameter.Size = size;
        command.Parameters.Add(parameter);
        return parameter;
    }

通过名称而不是索引访问DbDataReader字段:

    public static DateTime GetDateTime(this DbDataReader reader, string name)
    {
        int i = reader.GetOrdinal(name);
        return reader.GetDateTime(i);
    }

    public static decimal GetDecimal(this DbDataReader reader, string name)
    {
        int i = reader.GetOrdinal(name);
        return reader.GetDecimal(i);
    }

    public static double GetDouble(this DbDataReader reader, string name)
    {
        int i = reader.GetOrdinal(name);
        return reader.GetDouble(i);
    }

    public static string GetString(this DbDataReader reader, string name)
    {
        int i = reader.GetOrdinal(name);
        return reader.GetString(i);
    }

    ...

另一种(不相关的)扩展方法使我可以在WinForms表单和控件上执行DragMove操作(如WPF),请参见此处


5

我在这里看到的大多数扩展方法示例都违背了最佳实践。扩展方法功能强大,但应谨慎使用。以我的经验,大多数情况下,使用老式语法的静态助手/实用程序类通常是更可取的。

对于Enums的扩展方法,有些话要说,因为它们不可能有方法。如果在与Enum相同的名称空间中以及在同一程序集中定义它们,则它们将透明地工作。


5

虽然非常简单,但是我发现这是特别有用的,因为我从整个结果集中获得的页面是一个项目的一百亿倍:

public static class QueryableExtensions
{
    public static IQueryable<T> Page(this IQueryable<T> query, int pageNumber, int pageSize)
    {
        int skipCount = (pageNumber-1) * pageSize;
        query = query.Skip(skipCount);
        query = query.Take(pageSize);

        return query;
    }
}

为什么不将其写成一行?
nawfal 2012年

@nawfal:在一行上写什么?这是一个方便的扩展,您必须计算skipCount..so ....?
jrista 2012年

我认为这将更好地阅读:return query.Skip((pageNumber - 1) * pageSize).Take(pageSize)。只是一个口味的问题..
nawfal 2012年

@nawfal:好吧,您可以随意重新格式化。就个人而言,我喜欢破坏我的陈述,即出于可调试性的考虑(即,我可以在int skipCount行上设置一个断点并事先评估该数字,而如果将它们全部组合成一行,则可调试性会大大下降。查询也是如此每行变量...我可以逐步执行代码来独立评估结果。)
jrista 2012年

好吧,我同意int skipcount行。但是Linq应该排成一行,因为将cos分为两行对您毫无帮助:)只是我的想法
nawfal 2012年

5

通常,我需要基于Enum值显示一个用户友好的值,但由于它看起来不太优雅,所以不想走自定义Attribute路线。

使用这种方便的扩展方法:

public static string EnumValue(this MyEnum e) {
    switch (e) {
        case MyEnum.First:
            return "First Friendly Value";
        case MyEnum.Second:
            return "Second Friendly Value";
        case MyEnum.Third:
            return "Third Friendly Value";
    }
    return "Horrible Failure!!";
}

我可以做这个:

Console.WriteLine(MyEnum.First.EnumValue());

好极了!


5

这是在引发事件之前集中进行空检查的扩展方法。

public static class EventExtension
{
    public static void RaiseEvent<T>(this EventHandler<T> handler, object obj, T args) where T : EventArgs
    {
        EventHandler<T> theHandler = handler;

        if (theHandler != null)
        {
            theHandler(obj, args);
        }
    }
}

+1。但是在检查是否为空之前,您应该首先将事件处理程序的实例复制到局部变量(事件处理程序实例是不可变的)。这将防止在多线程环境中出现某些潜在的竞争情况(在无效检查之后但在调用处理程序之前,另一个线程可能已取消订阅)
Yann Trevin 2010年

那是真实的。我用自己的代码执行此操作,但从未回来更改此答案。我编辑了答案。
泰勒·莱斯

1
@Yann Trevin:这是不必要的,因为将EventHandler <T>对象作为参数传递给扩展方法时已经复制了一次,因此没有竞争条件。
ShdNx

3

这非常简单,但是我做了很多检查,所以最终为它制作了扩展方法。我最喜欢的扩展方法往往是像这样真正简单,直接的方法,或者像泰勒L的引发事件的扩展方法一样。

public static bool IsNullOrEmpty(this ICollection e)
{
    return e == null || e.Count == 0;
}

你可以使用IEnumerable,而不是ICollection
WiiMaxx

2

要允许更多功能的组合器代码:

    public static Func<T, R> TryCoalesce<T, R>(this Func<T, R> f, R coalesce)
    {
        return x =>
            {
                try
                {
                    return f(x);
                }
                catch
                {
                    return coalesce;
                }
            };
    }
    public static TResult TryCoalesce<T, TResult>(this Func<T, TResult> f, T p, TResult coalesce)
    {
        return f.TryCoalesce(coalesce)(p);
    }

然后我可以这样写:

    public static int ParseInt(this string str, int coalesce)
    {
        return TryCoalesce(int.Parse, str, coalesce);
    }

问题是,这将使您的代码在遇到致命异常(AccessViolation,ExecutionEngineException,OutOufMemoryException等)时变得不可靠,因为auf是不合格的“ catch”。
Christian.K 2010年

1
大多数代码在内存

2

我经常使用的另一组方法是合并IDictionary方法:

    public static TValue Get<TKey, TValue>(this IDictionary<TKey, TValue> d, TKey key, Func<TValue> valueThunk)
    {
        TValue v = d.Get(key);
        if (v == null)
        {
            v = valueThunk();
            d.Add(key, v);
        }
        return v;
    }
    public static TValue Get<TKey, TValue>(this IDictionary<TKey, TValue> d, TKey key, TValue coalesce)
    {
        return Get(d, key, () => coalesce);
    }

对于一般的集合来说:

    public static IEnumerable<T> AsCollection<T>(this T item)
    {
        yield return item;
    }

然后对于树状结构:

    public static LinkedList<T> Up<T>(this T node, Func<T, T> parent)
    {
        var list = new LinkedList<T>();
        node.Up(parent, n => list.AddFirst(n));
        return list;
    }

这样一来,我就可以轻松遍历并操作像以下这样的类:

class Category
{
    public string Name { get; set; }
    public Category Parent { get; set; }
}

接下来,为了便于函数组合和更类似于F#的C#编程方式:

public static Func<T, T> Func<T>(this Func<T, T> f)
{
    return f;
}
public static Func<T1, R> Compose<T1, T2, R>(this Func<T1, T2> f, Func<T2, R> g)
{
    return x => g(f(x));
}

2

我正在将很多Java转换为C#。许多方法仅在大小写或其他小的语法差异上有所不同。因此Java代码例如

myString.toLowerCase();

不会编译,但是会添加扩展方法

public static void toLowerCase(this string s)
{
    s.ToLower();
}

我可以捕获所有方法(而且我想一个好的编译器无论如何都会内联?)。

当然,这使工作变得更加轻松和可靠。(我感谢@Yuriy-请参见答案:Java中的StringBuilder与C#之间的区别)。


1

我喜欢这个。它是String.Split方法的一种变体,它允许将转义字符用于实际字符串时使用转义字符来抑制拆分。


1

int的扩展方法,用于将指定天(在这种情况下,星期的第一天为星期一)的位掩码解码为DayOfWeek枚举:

public static IEnumerable<DayOfWeek> Days(this int dayMask)
{
    if ((dayMask & 1) > 0) yield return DayOfWeek.Monday;
    if ((dayMask & 2) > 0) yield return DayOfWeek.Tuesday;
    if ((dayMask & 4) > 0) yield return DayOfWeek.Wednesday;
    if ((dayMask & 8) > 0) yield return DayOfWeek.Thursday;
    if ((dayMask & 16) > 0) yield return DayOfWeek.Friday;
    if ((dayMask & 32) > 0) yield return DayOfWeek.Saturday;
    if ((dayMask & 64) > 0) yield return DayOfWeek.Sunday;
}

为什么在这里使用收益?我很好奇这是如何工作的...如果您有星期一和星期五,即0010001作为您的dayMask,那么使用收益率回报与收益率会有什么影响?
杰夫·肉丸杨

啊-我明白了-它基本上是在您遍历位掩码时从位掩码中中断的地方开始的。得到它了。
杰夫·肉丸杨

1

这创建了一个数组,并在开始时添加了单个元素:

public static T[] Prepend<T>(this T[] array, T item)
{
    T[] result = new T[array.Length + 1];
    result[0] = item;
    Array.Copy(array, 0, result, 1, array.Length);
    return result;
}

string[] some = new string[] { "foo", "bar" };
...
some = some.Prepend("baz"); 

当我需要将一些表达式转换为平方时,这对我有帮助:

public static double Sq(this double arg)
{
    return arg * arg;
}

(x - x0).Sq() + (y - y0).Sq() + (z - z0).Sq()

3
kes!阵列上的前置是这样一个坏主意,在概念上。一定要确保/需要此操作...将其设为扩展方法,可以通过在初始为空(或小的)数组之前添加一大组值,从而很容易陷入填充大数组的陷阱中过程中每个中间数组的临时副本!就我个人而言,我不会这样做。(同去的一个数组追加,如果你有。)
peSHIr

1
我确定我需要这个。广泛使用数组的代码会使用此代码,并且修改很少发生。
okutane

我同意这是您通常应谨慎使用的东西。也许您可以包含一个可以接受多个项作为前缀的重载,以避免在循环中调用您的方法。
德鲁·诺阿克斯

1
如果必须使用数组,像这样的操纵器将很有帮助。如果您的同事可能会错误地使用它,则可以给它起一个更能表明其缺点的名称,例如“ PrependSlow”或“ PrependAndCopy”。我喜欢+1
Michael Haren

1

这是我写的另一个:

    public static class StringExtensions
    {
        /// <summary>
        /// Returns a Subset string starting at the specified start index and ending and the specified end
        /// index.
        /// </summary>
        /// <param name="s">The string to retrieve the subset from.</param>
        /// <param name="startIndex">The specified start index for the subset.</param>
        /// <param name="endIndex">The specified end index for the subset.</param>
        /// <returns>A Subset string starting at the specified start index and ending and the specified end
        /// index.</returns>
        public static string Subsetstring(this string s, int startIndex, int endIndex)
        {
            if (startIndex < 0) throw new ArgumentOutOfRangeException("startIndex", "Must be positive.");
            if (endIndex < 0) throw new ArgumentOutOfRangeException("endIndex", "Must be positive.");
            if (startIndex > endIndex) throw new ArgumentOutOfRangeException("endIndex", "Must be >= startIndex.");
            return s.Substring(startIndex, (endIndex - startIndex));
        }

        /// <summary>
        /// Finds the specified Start Text and the End Text in this string instance, and returns a string
        /// containing all the text starting from startText, to the begining of endText. (endText is not
        /// included.)
        /// </summary>
        /// <param name="s">The string to retrieve the subset from.</param>
        /// <param name="startText">The Start Text to begin the Subset from.</param>
        /// <param name="endText">The End Text to where the Subset goes to.</param>
        /// <param name="ignoreCase">Whether or not to ignore case when comparing startText/endText to the string.</param>
        /// <returns>A string containing all the text starting from startText, to the begining of endText.</returns>
        public static string Subsetstring(this string s, string startText, string endText, bool ignoreCase)
        {
            if (string.IsNullOrEmpty(startText)) throw new ArgumentNullException("startText", "Must be filled.");
            if (string.IsNullOrEmpty(endText)) throw new ArgumentNullException("endText", "Must be filled.");
            string temp = s;
            if (ignoreCase)
            {
                temp = s.ToUpperInvariant();
                startText = startText.ToUpperInvariant();
                endText = endText.ToUpperInvariant();
            }
            int start = temp.IndexOf(startText);
            int end = temp.IndexOf(endText, start);
            return Subsetstring(s, start, end);
        }
    }

这背后的动机很简单。总是让我感到困惑的是,内置的Substring方法是如何将startindex和length作为其参数的。进行startindex和endindex总是更有帮助。因此,我自己滚动了:

用法:

        string s = "This is a tester for my cool extension method!!";
        s = s.Subsetstring("tester", "cool",true);

我必须使用Subsetstring的原因是因为Substring的重载已经需要两个整数。如果有人的名字更好,请告诉我!


我不明白为什么我自己没有这样做。+1
Torbjørn

也许您可以使用SubstringBetween之类的名称,甚至可以使用Between之间的名称。
德鲁·诺阿克斯

4
@peSHlr:如果您想编辑帖子,那很好,但是实际上是在更改代码,因为您认为这是更好的例外,我认为这有点多。我不会回复,但是我认为这有点过头了。
BFree

1
这就是Wiki-ness!
Cheeso

1

酷,也爱扩展!

这是一些。

这将获得一个月的最后日期:

<System.Runtime.CompilerServices.Extension()> _
    Public Function GetLastMonthDay(ByVal Source As DateTime) As DateTime
        Dim CurrentMonth As Integer = Source.Month
        Dim MonthCounter As Integer = Source.Month
        Dim LastDay As DateTime
        Dim DateCounter As DateTime = Source

        LastDay = Source

        Do While MonthCounter = CurrentMonth
            DateCounter = DateCounter.AddDays(1)
            MonthCounter = DateCounter.Month

            If MonthCounter = CurrentMonth Then
                LastDay = DateCounter
            End If
        Loop

        Return LastDay
    End Function

这两个使反射变得容易一些:

 <System.Runtime.CompilerServices.Extension()> _
    Public Function GetPropertyValue(Of ValueType)(ByVal Source As Object, ByVal PropertyName As String) As ValueType
        Dim pInfo As System.Reflection.PropertyInfo

        pInfo = Source.GetType.GetProperty(PropertyName)

        If pInfo Is Nothing Then
            Throw New Exception("Property " & PropertyName & " does not exists for object of type " & Source.GetType.Name)
        Else
            Return pInfo.GetValue(Source, Nothing)
        End If
    End Function

    <System.Runtime.CompilerServices.Extension()> _
    Public Function GetPropertyType(ByVal Source As Object, ByVal PropertyName As String) As Type
        Dim pInfo As System.Reflection.PropertyInfo

        pInfo = Source.GetType.GetProperty(PropertyName)

        If pInfo Is Nothing Then
            Throw New Exception("Property " & PropertyName & " does not exists for object of type " & Source.GetType.Name)
        Else
            Return pInfo.PropertyType
        End If
    End Function

1
您可以将其分为两个单独的答案。就是这样!
Drew Noakes

1
... GetLastMonthDay实现太可怕了!一行:返回source.AddMonths(1).AddDays(-(source.Day + 1));
任务



1

我主要使用的一些扩展。首先是对象扩展,实际上仅用于转换。

public static class ObjectExtension
{
    public static T As<T>(this object value)
    {
        return (value != null && value is T) ? (T)value : default(T);
    }

    public static int AsInt(this string value)
    {
        if (value.HasValue())
        {
            int result;

            var success = int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result);

            if (success)
            {
                return result;
            }
        }

        return 0;
    }

    public static Guid AsGuid(this string value)
    {
        return value.HasValue() ? new Guid(value) : Guid.Empty;
    }
}

字符串扩展

public static class StringExtension
{
    public static bool HasValue(this string value)
    {
        return string.IsNullOrEmpty(value) == false;
    }

    public static string Slug(this string value)
    {
        if (value.HasValue())
        {
            var builder = new StringBuilder();
            var slug = value.Trim().ToLower();

            foreach (var c in slug)
            {
                switch (c)
                {
                    case ' ':
                        builder.Append("-");
                        break;
                    case '&':
                        builder.Append("and");
                        break;
                    default:

                        if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') && c != '-')
                        {
                            builder.Append(c);
                        }

                        break;
                }
            }

            return builder.ToString();
        }

        return string.Empty;
    }

    public static string Truncate(this string value, int limit)
    {
        return (value.Length > limit) ? string.Concat(value.Substring(0, Math.Min(value.Length, limit)), "...") : value;
    }
}

最后是一些枚举扩展

public static class EnumExtensions
{
    public static bool Has<T>(this Enum source, params T[] values)
    {
        var value = Convert.ToInt32(source, CultureInfo.InvariantCulture);

        foreach (var i in values)
        {
            var mask = Convert.ToInt32(i, CultureInfo.InvariantCulture);

            if ((value & mask) == 0)
            {
                return false;
            }
        }

        return true;
    }

    public static bool Has<T>(this Enum source, T values)
    {
        var value = Convert.ToInt32(source, CultureInfo.InvariantCulture);
        var mask = Convert.ToInt32(values, CultureInfo.InvariantCulture);

        return (value & mask) != 0;
    }

    public static T Add<T>(this Enum source, T v)
    {
        var value = Convert.ToInt32(source, CultureInfo.InvariantCulture);
        var mask = Convert.ToInt32(v, CultureInfo.InvariantCulture);

        return Enum.ToObject(typeof(T), value | mask).As<T>();
    }

    public static T Remove<T>(this Enum source, T v)
    {
        var value = Convert.ToInt32(source, CultureInfo.InvariantCulture);
        var mask = Convert.ToInt32(v, CultureInfo.InvariantCulture);

        return Enum.ToObject(typeof(T), value & ~mask).As<T>();
    }

    public static T AsEnum<T>(this string value)
    {
        try
        {
            return Enum.Parse(typeof(T), value, true).As<T>();
        }
        catch
        {
            return default(T);
        }
    }
}

+1好极了!在处理字符串时,我不熟悉“塞子”一词。希望分享您将如何使用此特定方法?谢谢!
Mark Carpenter

2
基本上它将把“汽车和卡车”变成“汽车和卡车”
Mike Geise 09年

为什么不只调用HttpUtility.UrlEncode()?
tsilb 2010年

@tslib就是关于seo的全部内容,这就是为什么我们首先对字符串进行处理的原因。
Mike Geise 2010年

1

通过常规使用StringBuilder,您可能会发现需要结合AppendFormat()和AppendLine()。

public static void AppendFormatLine(this StringBuilder sb, string format, params object[] args)
{
    sb.AppendFormat(format, args);
    sb.AppendLine();
}

另外,由于我要将应用程序从VB6转换为C#,因此以下内容对我非常有用:

public static string Left(this string s, int length)
{
    if (s.Length >= length)
        return s.Substring(0, length);
    throw new ArgumentException("Length must be less than the length of the string.");
}
public static string Right(this string s, int length)
{
    if (s.Length >= length)
        return s.Substring(s.Length - length, length);
    throw new ArgumentException("Length must be less than the length of the string.");
}

1

从我自己的字符串utils个人收藏中,我最喜欢的是它将为任何具有TryParse方法的类型从字符串中解析强类型值的方法:

public static class StringUtils
{
    /// <summary>
    /// This method will parse a value from a string.
    /// If the string is null or not the right format to parse a valid value,
    /// it will return the default value provided.
    /// </summary>
    public static T To<t>(this string value, T defaultValue)
        where T: struct
    {
        var type = typeof(T);
        if (value != null)
        {
            var parse = type.GetMethod("TryParse", new Type[] { typeof(string), type.MakeByRefType() });
            var parameters = new object[] { value, default(T) };
            if((bool)parse.Invoke(null, parameters))
                return (T)parameters[1];
        }
        return defaultValue;
    }

    /// <summary>
    /// This method will parse a value from a string.
    /// If the string is null or not the right format to parse a valid value,
    /// it will return the default value for the type.
    /// </summary>
    public static T To<t>(this string value)
        where T : struct
    {
        return value.To<t>(default(T));
    }
}

从查询字符串中获取强类型信息非常有用:

var value = Request.QueryString["value"].To<int>();

1

我讨厌到处都必须这样做:

DataSet ds = dataLayer.GetSomeData(1, 2, 3);
if(ds != null){
    if(ds.Tables.Count > 0){
        DataTable dt = ds.Tables[0];
        foreach(DataRow dr in dt.Rows){
            //Do some processing
        }
    }
}

相反,我通常使用以下扩展方法:

public static IEnumerable<DataRow> DataRows(this DataSet current){
    if(current != null){
        if(current.Tables.Count > 0){
            DataTable dt = current.Tables[0];
            foreach(DataRow dr in dt.Rows){
                yield return dr;
            }
        }
    }
}

因此,第一个示例变为:

foreach(DataRow row in ds.DataRows()){
    //Do some processing
}

是的,扩展方法!


您的数据集多久有1张以上的表格?只需使用foreach(ds.Tables [0] .Rows中的DataRow dr)
tsilb 2010年

0

String.format不应该是静态的。因此,我使用一种称为frmt的扩展方法:

<Extension()> Public Function frmt(ByVal format As String,
                                   ByVal ParamArray args() As Object) As String
    If format Is Nothing Then Throw New ArgumentNullException("format")
    Return String.Format(format, args)
End Function

当我想在不构造二进制写程序的情况下向字节流读取或写入数字时(从技术上讲,在用写程序包装原始流后,不应该修改原始流):

<Extension()> Public Function Bytes(ByVal n As ULong,
                                    ByVal byteOrder As ByteOrder,
                                    Optional ByVal size As Integer = 8) As Byte()
    Dim data As New List(Of Byte)
    Do Until data.Count >= size
        data.Add(CByte(n And CULng(&HFF)))
        n >>= 8
    Loop
    Select Case byteOrder
        Case ByteOrder.BigEndian
            Return data.ToArray.reversed
        Case ByteOrder.LittleEndian
            Return data.ToArray
        Case Else
            Throw New ArgumentException("Unrecognized byte order.")
    End Select
End Function
<Extension()> Public Function ToULong(ByVal data As IEnumerable(Of Byte),
                                      ByVal byteOrder As ByteOrder) As ULong
    If data Is Nothing Then Throw New ArgumentNullException("data")
    Dim val As ULong
    Select Case byteOrder
        Case ByteOrder.LittleEndian
            data = data.Reverse
        Case ByteOrder.BigEndian
            'no change required
        Case Else
            Throw New ArgumentException("Unrecognized byte order.")
    End Select
    For Each b In data
        val <<= 8
        val = val Or b
    Next b
    Return val
End Function

0

这个移位序列,以便您首先获得给定的项目。例如,我用它来计算星期几并进行转换,以使序列中的第一天成为当前区域性的星期几。

    /// <summary>
    /// Shifts a sequence so that the given <paramref name="item"/> becomes the first. 
    /// Uses the specified equality <paramref name="comparer"/> to find the item.
    /// </summary>
    /// <typeparam name="TSource">Type of elements in <paramref name="source"/>.</typeparam>
    /// <param name="source">Sequence of elements.</param>
    /// <param name="item">Item which will become the first.</param>
    /// <param name="comparer">Used to find the first item.</param>
    /// <returns>A shifted sequence. For example Shift({1,2,3,4,5,6}, 3) would become {3,4,5,6,1,2}. </returns>
    public static IEnumerable<TSource> Shift<TSource>(this IEnumerable<TSource> source, TSource item, IEqualityComparer<TSource> comparer)
    {
        var queue = new Queue<TSource>();
        bool found = false;

        foreach (TSource e in source)
        {
            if (!found && comparer.Equals(item, e))
                found = true;

            if (found)
                yield return e;
            else
                queue.Enqueue(e);
        }

        while (queue.Count > 0)
            yield return queue.Dequeue();
    }


    /// <summary>
    /// Shifts a sequence so that the given item becomes the first. 
    /// Uses the default equality comparer to find the item.
    /// </summary>
    /// <typeparam name="TSource">Type of elements in <paramref name="source"/>.</typeparam>
    /// <param name="source">Sequence of elements.</param>
    /// <param name="element">Element which will become the first.</param>
    /// <returns>A shifted sequence. For example Shift({1,2,3,4,5,6}, 3) would become {3,4,5,6,1,2}. </returns>
    public static IEnumerable<TSource> Shift<TSource>(this IEnumerable<TSource> source, TSource element)
    {
        return Shift(source, element, EqualityComparer<TSource>.Default);
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.