如何重构这些通用方法?(关于IT技术)

5

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

我编写了两个几乎相同的通用扩展方法,并尝试弄清楚如何将它们重构为单个方法。它们之间唯一的区别在于一个操作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();
}

资产文档(AssetDocument)和人员文档(PersonDocument)是否派生自同一基类/接口? - Preets
7个回答

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(); }

哎呀,我错过了更明显的string.Join :-( - Dan Puzey
我喜欢这个解决方案。它不会改变调用代码,并将重复的代码减少到最低限度。我经常使用LINQ,但确实要记住,在自己编写代码之前,确保没有现成的LINQ方法可以完成需要的功能。 - Steve Crane

3

我认为解决方法是让PersonDocument和AssetDocument继承一个名为Document的类,该类将具有一个Id属性来存储当前的PersonId或AssetId。


这也很好,因为他已经有一个基类。即使属性在基类中声明,两个子类也可以创建自己的实现。 - Benny Jobigan

3

进行抽象化,例如创建一个 IDocument 或者一个抽象类 BaseDocument,暴露出id(这是唯一真正使用的字段),并使 PersonDocumentAssetDocument 都实现它。现在让你的通用方法接受 IDocumentBaseDocument


我也想建议这个。 - Benny Jobigan

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)。
更新:即使您想保留原始扩展方法,也可以使用此方法将其减少为一行代码。

将更多的复杂性转移到调用者身上,以节省一些重复的代码行,虽然可行,但并不是很有意义。 - Steve Crane

2

您的方法是否可以接受一个委托,以返回适当列表的document.AssetID.ToString()

使用Lambda表达式可以使代码变得更加简洁,尽管有些丑陋。以下是一个演示控制台应用程序:

    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();
    }
}

无论这是否是最佳方法,我将其留给读者作为练习!

将更多的复杂性转移到调用者身上,以节省一些重复的代码行,虽然可行,但并不是很有意义。 - Steve Crane
完全同意 - 这也是我的最后评论。虽然它增加了一些灵活性,但我无法想象它会有用 :) - Dan Puzey

1

我只会Java,所以无法提供正确的语法,但一般的方法应该可行:

定义一个接口Document,由PersonDocument和AssetDocument实现,其中包含方法

String getIdString();

使用List作为您方法的参数。请注意,这是一个继承/扩展自Document的Something类型的Java语法列表。

1

你可以使用反射来实现一些鸭子类型的操作!

我假设你的类名为#class#Document,你想要连接#class#ID属性。如果列表中包含符合这种命名规则的类,它们将被连接起来。否则,它们不会被连接。

这非常类似于Rails框架的操作方式,使用约定优于配置

显然,这种行为更适用于动态语言,如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 ();
    }
}

用法(请注意,由于鸭子类型,不需要继承-也适用于任何类型!):

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 () );

...


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