【问题标题】:How to refactor these generic methods?如何重构这些泛型方法?
【发布时间】:2010-03-12 10:27:47
【问题描述】:

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

我已经编写了两个几乎相同的通用扩展方法,并试图弄清楚如何将它们重构为一个方法。它们的区别仅在于一个在 List 上运行,另一个在 List 上运行,我感兴趣的属性是 AssetID 的 AssetDocument 和 PersonID 的 PersonDocument。尽管 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();
}

【问题讨论】:

  • AssetDocument 和 PersonDocument 是否派生自一个公共基类/接口?

标签: c# generics refactoring


【解决方案1】:

您的实现基本上是重新实现 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(); }

【讨论】:

  • Doh,我错过了更明显的字符串。加入 :-(
  • 我喜欢这个解决方案。它不会更改调用代码并将重复代码减少到最低限度。我经常使用 LINQ,但真的必须记住确保没有 LINQ 方法,然后再编写自己的方法来做某事。
【解决方案2】:

我认为方法是让 PersonDocument 和 AssetDocument 从 Document 类继承,该类具有 Id 属性,分别存储您当前的 PersonId 或 AssetId。

【讨论】:

  • 这也很好,因为他已经有了一个基类。即使在基类中声明了属性,两个子类也可以创建自己的实现。
【解决方案3】:

创建一个抽象,例如IDocument 或抽象类BaseDocument,它公开id(这是您真正使用的唯一字段),并使PersonDocumentAssetDocument 都实现它。现在让你的泛型方法接受IDocumentBaseDocument

【讨论】:

  • 我也打算推荐这个。
【解决方案4】:

你觉得这个变体怎么样(有点简化,但你应该明白):

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)。

更新:即使你想保留原来的扩展方法,你也可以用这个将它们减少到一行代码。

【讨论】:

  • 可行但将更多复杂性转移给调用者以保存几行重复的代码并没有真正意义。
【解决方案5】:

如何让您的方法也接受一个委托,以适当地返回该列表的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();
    }
}

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

【讨论】:

  • 可行但将更多复杂性转移给调用者以保存几行重复的代码并没有真正意义。
  • 完全同意 - 因此我的最终评论。虽然它确实增加了一些灵活性,但我无法想象它会有用:)
【解决方案6】:

我只知道java,所以我不能给出正确的语法,但是一般的方法应该可以:

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

String getIdString();

使用列表作为方法的参数。请注意,这是从 Document 继承/扩展的某物列表的 java 语法。

【讨论】:

    【解决方案7】:

    您可以使用反射进行一些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 () );
    

    ...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多