如何重构这些通用方法?

时间:2010-03-12 10:27:47

标签: c# generics refactoring

更新:我应该在原帖中提到我想在这里了解更多有关泛型的信息。我知道这可以通过修改基类或创建两个文档类实现的接口来完成。但是为了这个练习,我只对那些不需要对文档类或它们的基类进行任何修改的解决方案感兴趣。我认为这个问题涉及扩展方法的事实会暗示这一点。

我编写了两个几乎相同的通用扩展方法,并试图弄清楚如何将它们重构为单个方法。它们的区别仅在于,一个在List上运行,另一个在List上运行,我感兴趣的属性是AssetDocument的AssetID和PersonDocument的PersonID。尽管AssetDocument和PersonDocument具有相同的基类,但每个类中都定义了属性,因此我认为这没有帮助。我试过了

public static string ToCSVList<T>(this T list) where T : List<PersonDocument>, List<AssetDocument>

认为我可能会测试类型并采取相应的行动,但这会导致语法错误

  

类型参数'T'继承   冲突的约束

这些是我想要重构为单一方法的方法,但也许我只是过分了,他们最好保持原样。我想听听你的想法。

public static string ToCSVList<T>(this T list) where T : List<AssetDocument>
{
  var sb = new StringBuilder(list.Count * 36 + list.Count);
  string delimiter = String.Empty;

  foreach (var document in list)
  {
    sb.Append(delimiter + document.AssetID.ToString());
    delimiter = ",";
  }

  return sb.ToString();
}

public static string ToCSVList<T>(this T list) where T : List<PersonDocument>
{
  var sb = new StringBuilder(list.Count * 36 + list.Count);
  string delimiter = String.Empty;

  foreach (var document in list)
  {
    sb.Append(delimiter + document.PersonID.ToString());
    delimiter = ",";
  }

  return sb.ToString();
}

7 个答案:

答案 0 :(得分:7)

你的实现基本上是重新实现了string.Join方法,所以你可能会尝试使用一些LINQ使它变得更简单和更通用:

public static string ToCSVList<T>(this IEnumerable<T> collection)
{ return string.Join(",", collection.Select(x => x.ToString()).ToArray()); }

public static string ToCSVList(this IEnumerable<AssetDocument> assets)
{ return assets.Select(a => a.AssetID).ToCSVList(); }

public static string ToCSVList(this IEnumerable<PersonDocument> persons)
{ return persons.Select(p => p.PersonID).ToCSVList(); }

答案 1 :(得分:3)

我认为让方法是让PersonDocument和AssetDocument继承一个Document类,它有一个Id属性,可以分别存储你当前的PersonId或AssetId。

答案 2 :(得分:3)

创建一个抽象,例如IDocument或抽象类BaseDocument,它公开id(这是你真正使用的唯一字段)并同时生成PersonDocument和{{1实现它。现在让您的通用方法接受AssetDocumentIDocument

答案 3 :(得分:2)

你喜欢这个变种(有点简化,但你应该明白):

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            var la = new List<AssetDocument> { new AssetDocument() {AssetID = 1} };

            var result = la.ToCSVList(l => l.AssetID.ToString());
        }
    }

    public class AssetDocument
    {
        public int AssetID { get; set; }
    }

    public static class GlobalExtensions
    {
        public static string ToCSVList<T>(this List<T> list, Func<T, string> propResolver)
        {
            var sb = new StringBuilder(list.Count * 36 + list.Count);
            var delimiter = "";

            foreach (var document in list)
            {
                sb.Append(delimiter);
                sb.Append(propResolver(document));
                delimiter = ",";
            }

            return sb.ToString();
        }
    }
}

这适用于任何列表(如果您不关心StringBuilder中的预分配内存,即使有任何IEnumerable)。

更新:即使您想保留原始扩展方法,也可以将它们缩减为一行代码。

答案 4 :(得分:2)

如何让您的方法也接受委托,以便在适当时返回该列表的document.AssetID.ToString()

使用Lamda表达式,如果有点难看,可能会相当轻巧。一个用于演示的控制台应用程序:

    class Program
    {
    static void Main(string[] args)
    {
        List<string> strings = new List<string> { "hello", "world", "this", "is", "my", "list" };
        List<DateTime> dates = new List<DateTime> { DateTime.Now, DateTime.MinValue, DateTime.MaxValue };

        Console.WriteLine(ToCSVList(strings, (string s) => { return s.Length.ToString(); }));
        Console.WriteLine(ToCSVList(dates, (DateTime d) => { return d.ToString(); }));

        Console.ReadLine();
    }

    public static string ToCSVList<T, U>(T list, Func<U, String> f) where T : IList<U>
    {
        var sb = new StringBuilder(list.Count * 36 + list.Count);
        string delimiter = String.Empty;

        foreach (var document in list)
        {
            sb.Append(delimiter + f(document));
            delimiter = ",";
        }

        return sb.ToString();
    }
}

这是否是最好的方法,我留给读者作为练习!

答案 5 :(得分:1)

我只知道java,所以我无法给出正确的语法,但一般方法应该有效:

定义一个接口Document,由PersonDocument和AssetDocument实现, 用方法

String getIdString();

使用List作为方法的参数。请注意,这是从Document继承/扩展的List of Something的java语法。

答案 6 :(得分:1)

您可以使用Reflection进行一些Duck Typing操作!

我假设您的类被称为#class #Document,并且您希望连接#class #ID属性。如果列表包含符合此命名的类,则它们将被连接。否则他们不会。

这是Rails框架使用Convention over Configuration进行操作的方式。

显然,这种行为更适合Ruby等动态语言。对于更静态的语言(如C#)来说,最好的解决方案可能是重构基类,使用接口等。但这不在规范中,出于教育目的,这是一种解决问题的方法!

public static class Extensions
{
    public static string ToCSVList<T> ( this T list ) where T : IList
    {
        var sb = new StringBuilder ( list.Count * 36 + list.Count );
        string delimiter = String.Empty;

        foreach ( var document in list )
        {
            string propertyName = document.GetType ().Name.Replace("Document", "ID");
            PropertyInfo property = document.GetType ().GetProperty ( propertyName );
            if ( property != null )
            {
                string value = property.GetValue ( document, null ).ToString ();

                sb.Append ( delimiter + value );
                delimiter = ",";
            }
        }

        return sb.ToString ();
    }
}

用法(注意不需要使用Duck Typing进行继承 - 也适用于任何类型!):

public class GroovyDocument
{
    public string GroovyID
    {
        get;
        set;
    }
}

public class AssetDocument
{
    public int AssetID
    {
        get;
        set;
    }
}

...

        List<AssetDocument> docs = new List<AssetDocument> ();
        docs.Add ( new AssetDocument () { AssetID = 3 } );
        docs.Add ( new AssetDocument () { AssetID = 8 } );
        docs.Add ( new AssetDocument () { AssetID = 10 } );

        MessageBox.Show ( docs.ToCSVList () );

        List<GroovyDocument> rocs = new List<GroovyDocument> ();
        rocs.Add ( new GroovyDocument () { GroovyID = "yay" } );
        rocs.Add ( new GroovyDocument () { GroovyID = "boo" } );
        rocs.Add ( new GroovyDocument () { GroovyID = "hurrah" } );

        MessageBox.Show ( rocs.ToCSVList () );

...