ОБНОВЛЕНИЕ: Я должен был упомянуть в исходном сообщении, что хочу узнать больше о дженериках здесь. Я знаю, что это может быть сделано путем изменения базового класса или создания интерфейса это обе реализации классов документов. Но ради этого осуществления я только действительно интересуюсь решениями, которые не требуют никакой модификации к классам документов или их базовому классу. Я думал, что то, что вопрос включает дополнительные методы, подразумевало бы это.
Я записал два почти идентичных универсальных дополнительных метода, и пытаюсь выяснить, как я мог бы осуществить рефакторинг их в отдельный метод. Они отличаются только, в котором воздействует на Список и другой в Списке, и свойствами, которыми я интересуюсь, является 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();
}
Ваша реализация в основном перевыполнеет строку. Метод 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(); }
Как вам этот вариант (немного упрощенный, но вы должны уловить идею):
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 ).
Обновление: даже если вы хотите сохранить исходные методы расширения, вы можете сократить их до одной строчки кода.
Я знаю только java, поэтому я не могу указать правильный синтаксис, но общий подход должен работать:
определить интерфейс Document, который будет реализован от PersonDocument и AssetDocument, с методом
String getIdString();
Используйте список в качестве параметра вашего метода. Обратите внимание, что это синтаксис java для списка чего-то, что наследуется / расширяется от документа.
Я думаю, что можно было бы разрешить PersonDocument и AssetDocument наследовать класс Document, у которого будет свойство Id, в котором хранится ваш текущий PersonId или AssetId соответственно.
Создайте абстракцию, например IDocument
или абстрактный класс BaseDocument
, который предоставляет идентификатор (который является только поле, которое вы действительно используете) и сделайте так, чтобы оба PersonDocument
и AssetDocument
реализовали это. Теперь пусть ваш универсальный метод принимает вместо него IDocument
или BaseDocument
.
А как насчет того, чтобы ваш метод также принимал делегата для возврата 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();
}
}
Независимо от того, лучший это подход или нет, я оставляю читателю в качестве упражнения!
Отражение можно использовать для небольшого действия Duck Typing !
Я предположил, что ваши классы называются # 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 ();
}
}
Использование (обратите внимание, нет необходимости в наследовании с 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 () );
...