字符串插值:如何使此函数适用于任何类型

String interpolation: How do I make this function work with any type

这是一个在字符串插值中处理列表的函数。它采用一个 List 和一个内部 Func,并附加为列表的每个成员调用的内部 Func 的字符串结果,并带有一个分隔符。

所以下面构建了 Insert 语句的有效开始...

static void Main(string[] args)
{
    var tableName = "customers";
    var cols = new List<dynamic>
    {
        new { Name = "surname"},
        new { Name = "firstname"},
        new { Name = "dateOfBirth"}
    };
    Func<List<dynamic>, Func<dynamic, string>, string, string> ForEach = (list, func, separator) =>
        {
            var bldr = new StringBuilder();
            var first = true;
            foreach (var obj in list)
            {
                if (!first)
                    bldr.Append(separator);
                first = false;
                bldr.Append(func(obj));
            }
            return bldr.ToString();
        };

    var InsertStatement = $"Insert into { tableName } ( {ForEach(cols, col => col.Name, ", ")} )";
    Console.WriteLine(InsertStatement);
    Console.ReadLine();
}

输出...

Insert into customers ( surname, firstname, dateOfBirth )

它适用于动态。我如何让它适用于任何类型?外部 Func 不应该关心列表中的类型,它只是将它传递给内部 Func。

dynamic 替换为 object,或将 TValue 替换为类型约束,规定它必须是 class (where TValue : class),然后调用 obj.ToString() 而不仅仅是 obj

但是,这并不能保证它会 "work with any type" - 因为您需要知道这些类型都遵循约定输出所需的列名称作为它们的字符串表示形式。为了获得更多的特异性,要求您接受的类型必须实现一些接口,例如 IColumnName 并将该接口放入类型约束而不是

.NET 框架已经为您提供了一个通用函数来实现您想要做的事情String.Join,您可以将它与 LINQ Select 语句结合使用,这将允许您使用将通用类型上的 lambda 转换为 select 您要打印的 属性。这些方法都是开源的,有兴趣的可以查看源码。

using System;
using System.Collections.Generic;
using System.Linq;

public class MyType
{
   public string Name { get; set; } 
}

public class Program
{
    public static void Main()
    {
        var tableName = "customers";
        var cols = new List<MyType>
        {
            new MyType { Name = "surname"},
            new MyType { Name = "firstname"},
            new MyType { Name = "dateOfBirth"}
        };

        var InsertStatement = $"Insert into { tableName } ( {String.Join(", ", cols.Select(col => col.Name))} )";
        Console.WriteLine(InsertStatement);
    }
}

您可以像这样轻松创建文本:

var query = $"INSERT INTO {tableName}({string.Join(",", cols.Select(x=>x.Name))})";

但是,如果出于学习目的您打算使用泛型方法处理这种情况,您可以创建如下所示的泛型函数,然后轻松使用 for 使用 TrimEnd 循环并去除额外的分隔符,或者作为更好的选择,例如 String.Join implementation of .NET Framework 像这样获取枚举器:

string Join<TItem>(
    IEnumerable<TItem> items, Func<TItem, string> itemTextSelecor, string separator)
{
    var en = items.GetEnumerator();
    if (!en.MoveNext())
        return String.Empty;
    var builder = new StringBuilder();
    if (en.Current != null)
        builder.Append(itemTextSelecor(en.Current));
    while (en.MoveNext())
    {
        builder.Append(separator);
        if (en.Current != null)
            builder.Append(itemTextSelecor(en.Current));
    }
    return builder.ToString();
}

并这样使用:

var tableName = "customers";
var cols = new[]
{
    new { Name = "surname"},
    new { Name = "firstname"},
    new { Name = "dateOfBirth"}
};

var InsertStatement = $"INSERT INTO {tableName} ({Join(cols, col => col.Name, ", ")})" 
    +  $"VALUES({Join(cols, col => $"@{col.Name}", ", ")})";