2010-03-12 53 views
5

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

我写了两个几乎相同的通用扩展方法,并试图弄清楚如何将它们重构为单一方法。它们的不同之处仅在于列表和列表上的其他操作,而我感兴趣的属性是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(); 
} 
+0

做AssetDocument和PersonDocument派生自一个通用的基类/接口? – Preets 2010-03-12 10:31:34

回答

7

你的实现基本上是重新实现字符串。加入方法,所以你可以尝试让它更简单,更通用一些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(); } 
+0

Doh,我错过了更明显的字符串。加入:-( – 2010-03-12 11:24:37

+0

你并不孤单:-) – TToni 2010-03-12 11:50:27

+0

我喜欢这个解决方案。它不会更改调用代码并将重复代码减少到最低限度。我使用LINQ相当多,但是确实必须记住在脱机之前确保没有LINQ方法,并写我自己的东西来做一些事情。 – 2010-03-12 12:15:01

3

我的思维方式是让PersonDocument和AssetDocument从文档类,这将有一个ID属性,存储当前的PERSONID或由assetid respectivly继承。

+0

这也很好,因为他已经有了一个基类。即使该属性是在基类中声明的,两个子类都可以创建它们自己的实现。 – 2010-03-12 10:36:12

+0

很好的答案,但请参阅我上面的更新。 – 2010-03-12 10:46:26

3

让一个抽象,如IDocument或抽象类BaseDocument暴露的ID(这是你真正使用的唯一字段),使两者PersonDocumentAssetDocument实现这一点。现在让您的通用方法接受IDocumentBaseDocument

+0

我也想提出这个建议。 – 2010-03-12 10:34:36

+0

很好的答案,但请参阅我上面的更新。 – 2010-03-12 10:45:39

1

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

定义一个接口文件,该文件被由PersonDocument和AssetDocument, 与方法

String getIdString(); 
实施

使用列表作为参数给你的方法。请注意,这是从Document继承/扩展的某个List的Java语法。

+0

很好的答案,但请参阅我上面的更新。 – 2010-03-12 10:46:45

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

这将与任何列表工作(如果你不关心的预分配内存甚至可以使用任何IEnumerable的StringBuilder)。

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

+0

可行,但让调用者更复杂以节省一些重复的代码行并没有什么意义。 – 2010-03-12 12:11:18

2

怎么样让你的方法也采取委托返回document.AssetID.ToString()该列表为合适?

使用Lamda表达式,如果有点难看,这可能是相当轻量级的。一个控制台应用程序demonstarate:

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

这是否是最好的方法还是不行,我离开作为练习读者!

+0

可行,但让调用者更复杂以节省一些重复的代码行并没有什么意义。 – 2010-03-12 12:09:34

+0

完全同意 - 因此我的最终评论。虽然它确实增加了一些灵活性,但我无法想象它会有用:) – 2010-03-12 12:20:21

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

使用(注意不需要用鸭打字继承 - 也适用于任何类型!):

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

...