如何查找类型的属性和字段,然后从该类型的集合中读取它们?

问题描述 投票:0回答:3

我有T的列表,需要创建一个由“\ t”连接的字符串,该字符串具有此类型的多个属性。

string str = "1,2,3;2,3,4;4,5,6";
var arr = str
    .Split(';')
    .Select(x => x.Split(','))
    .Select(x => new
    {
        first = x[0],
        second = x[1],
        third = x[2]
    })
    .ToList();

现在arr是:

Enumerable.WhereSelectArrayIterator<string, <>f__AnonymousType0#10<string, string, string>> { \{ first = "1", second = "2", third = "3" }, \{ first = "2", second = "3", third = "4" }, \{ first = "4", second = "5", third = "6" } }

现在,如果我需要得到

List<string>(3) { "1\t2\t3", "2\t3\t4", "4\t5\t6" }

我应该这样做

arr.Select(x => x.first + "\t" + x.second + "\t" + x.third).ToList();

我需要扩展方法,允许我这样做

arr.Select(x => x.first, x.second, x.third).ToList();

并获得相同的结果。

我有扩展方法,通过选项卡连接对象的所有字段

public static IEnumerable<string> JoinByTab<T>(this IEnumerable<T> list) where T : class
{
    PropertyInfo[] properties = list.FirstOrDefault().GetType().
        GetProperties(BindingFlags.Public | BindingFlags.Instance);

    var listStr = list
        .Select(x => String.Join("\t", properties.Select(y => y.GetValue(x, null).ToString())));

    return listStr;
}

但我经常需要指定字段。

c# extension-methods
3个回答
0
投票

你可以将一堆lambdas传递给join方法,例如:

public static IEnumerable<string> Join<T>(this IEnumerable<T> list,
    string separator,
    params Func<T, string>[] properties) where T : class
{
    foreach (var element in list)
    {
        yield return string.Join(separator, properties.Select(p => p(element)));
    }
}

并像这样使用它:

var result = arr.Join("\t", 
        x => x.first, 
        x => x.second, 
        x => x.third)
    .ToList();

0
投票

但我经常需要指定字段。

您经常需要使用制表符分隔符连接对象字段吗?你甚至有扩展方法吗?哇,这是一个有趣的项目。

您只需从对象创建一个数组并使用常规string.Join

arr.Select(x => string.Join("\t", new[] { x.first, x.second, x.third }).ToList();

不需要扩展方法


0
投票

看起来你正试图从string类型中获取所有T属性和字段。然后对于一组T中的每个项目,您想要读取所有这些字符串属性,然后加入这些字符串。您将从T列表开始,以string列表结尾。

如果从执行反射的代码部分(查找属性和字段)中分离出读取对象列表的部分,则会变得更容易。

由于列表中的所有对象的属性和字段相同(因为它们都是相同的类型),因此您尝试一次发现属性和字段,然后重复使用它们。非常好。

所以问题是如何做到这一点。这有两种方式。

第一种类型(T') and returns all of the public instance properties and fields that returnstring.PropertyInfoandMemberInfoboth inherit fromMemberInfo, so we're returning a set ofMemberInfo`。

public static IEnumerable<MemberInfo> GetStringMembers<T>()
{
    var result = new List<MemberInfo>();
    result.AddRange(
            typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(property => property.PropertyType == typeof(string)));
    result.AddRange(
        typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance)
            .Where(field => field.FieldType == typeof(string)));
    return result;
}

问题是现在列表中的每个项目都可以是PropertyInfoFieldInfo,所以当我们尝试从每个类实例中获取每个值时,我们需要查看每个成员并查看它是属性还是字段,因为它是不同的阅读每一个的方法。那太乱了。 (我知道,因为我写了确切的代码。)

更好的方法是创建一个函数列表,它采用T并返回一个字符串。在每个函数内部,我们可以检查属性或字段。这看起来像这样。我没有使用LINQ就更容易阅读:

private static IEnumerable<Func<T, string>> GetStringMemberFunctions<T>()
{
    var result = new List<Func<T, string>>();
    var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
    foreach (var property in properties)
    {
        result.Add(item => (string)property.GetValue(item));
    }
    var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
    foreach (var field in fields)
    {
        result.Add(item => (string)field.GetValue(item));
    }
    return result;
}

我们返回的是Func<T, string>列表。这是一个功能列表,采取T并返回string。这些功能中的每一个都检查属性或字段。 (Resharper可以为我转换为LINQ表达式,但我不发布它因为它更难阅读所以我认为它没有帮助。)

现在,您的原始方法不包含所有反射。它允许另一个方法处理它,它看起来像这样:

public static IEnumerable<string> JoinByTab<T>(this IEnumerable<T> list) where T : class
{
    var functions = GetStringMemberFunctions<T>();

    var listStr = list
        .Select(x => String.Join("\t", functions.Select(function => function(x))));

    return listStr;
}

它完全相同,除了

  • 您不是使用反射查找属性,而是从另一个方法获取函数列表。
  • 而不是读取每个属性,而是调用每个方法。

你也可以更进一步。您可以创建一个将所有函数组合在一起的新函数,而不是获取函数列表并将它们全部调用以构建字符串列表。那个函数会调用所有其他函数并返回所有字符串。

public static Func<T, string[]> GetExtractStringValuesFunction<T>()
{
    var memberFunctions = GetStringMemberFunctions<T>();
    return item => memberFunctions.Select(function => function(item)).ToArray();
}

public static IEnumerable<string> JoinByTab<T>(this IEnumerable<T> list) where T : class
{
    var stringValuesFunction = GetExtractStringValuesFunction<T>();

    var listStr = list
        .Select(x => String.Join("\t", stringValuesFunction(x)));

    return listStr;
}

它调用GetStringMemberFunctions来获取读取成员的所有函数,然后将它们全部合并到一个Func<T, string[]>中。现在我们有一个函数,它接受T的一个实例并返回属性和字段中的所有字符串。

这只是一个较小的改进,仅用于说明。如果以前的版本很容易理解,那么这并没有带来很多好处。


边注:

这样做以获取属性和字段列表是不安全的,可能会导致异常:

list.FirstOrDefault().GetType()

列表中的所有项目都是T类型。但假设该列表中的任何单个项目也可以继承T。那么如果列表中的第一项是从T继承的类型并且具有列表中其他对象不具有的属性或字段,会发生什么?

GetType()将返回对象的具体类型。它可能正是T,但它可能是一些继承类型。您可以根据该类型创建属性和字段列表,但之后会因为尝试从没有这些属性或字段的对象中读取它们而获得异常。

这就是为什么,如果你有一个List<T>并且想要获得类型,那么使用typeof(T)会更安全。

(你问题中的代码意味着假设列表中的所有项目都应该被视为相同的类型。如果你想真正考虑从T继承的不同类型并且具有可能的不同属性和字段,那么只需稍微大一些蠕虫。)

© www.soinside.com 2019 - 2024. All rights reserved.