【问题标题】:C# workaround for tuple with names带有名称的元组的 C# 解决方法
【发布时间】:2012-10-22 16:18:05
【问题描述】:

我想要具有可以传递、比较和识别的命名成员的不可变匿名类型——元组和匿名类型的合并。 This doesn't exist,我意识到了这一点。

所以问题是:使用 C#4 或 5 有什么好的惯用替代方法?

用例是来自异构数据源的流畅 LINQ 数据处理。总之,C# 中的 ETL。我做了一些非常复杂的分析,数据来自多个平台和来源。 “将其全部放在同一个平台上并使用实体框架”不是一种选择。我希望能够流畅地传递本质上是任意的记录——不可变的命名只读属性集。

除了为每个单独的匿名类型创建自定义不可变 POCO 之外,我唯一能想到的方法是使用属性将编译的注释添加到返回 Tuples 的方法中。当然,编写一个代码生成器来吐出不可变的 POCO 并不难,但我讨厌这会使项目的对象模型变得混乱。使用dynamic 完全消除了静态类型的所有性能和设计时有用性,尤其是在从其他方法的结果中组合进一步查询时,所以我不认为它是一个可行的解决方案。

// My idea: Adding a attribute to methods to at least record the names
// of the "columns" of a Tuple at a method level
public class NamedTupleAttribute : Attribute {
    public string[] Names { get; private set; }
    public NamedTupleAttribute(string[] Names) { this.Names = Names; }
}

// Using NamedTuple attribute to note meaning of members of Tuple
[NamedTuple(new[] { "StoreNumber", "Gross", "Cost", "Tax" })]
public IEnumerable<Tuple<int, decimal, decimal, decimal>> GetSales { ... }

我想要什么(C# 6 的虚构 MSDN 文档):

鸭(C# 参考)

duck 关键字允许在 C# 的所有静态类型功能中使用匿名类型。与普通匿名类型一样,编译器会将具有相同数量、名称和属性类型的匿名类型视为具有相同类型。但是,duck 关键字还允许在成员声明中使用这些类型并作为泛型类型的类型参数。

1。鸭子类型实例

像匿名类型一样,duck 类型对象的实例只能使用对象初始化器创建 没有类型名称。语法与普通匿名类型相同,除了关键字 ducknew 运算符之后添加:

var record = new duck { StoreNumber=1204,
                        Transaction=410, 
                        Date=new DateTime(2012, 12, 13), 
                        Gross=135.12m, 
                        Cost=97.80m,
                        Tax=12.11m };

2。鸭子类型参考

可以使用duck 类型文字、duck 类型别名或隐式引用Duck 类型,当可以推断出属性或方法的返回类型时。

2.1 鸭子类型文字

duck 类型可以用类型文字来表示,它可以用来代替任何类型引用。 Duck 类型文字由关键字 duck 后跟名称 - 类型标识符对列表组成,就像在方法的参数列表中一样,除了用花括号括起来:

// A duck type literal:
duck { int StoreNumber, int Transaction, DateTime Date, decimal Gross, decimal Cost, decimal Tax }

// In a member declaration:
public duck { int StoreNumber, int Transaction, DateTime Date, 
              decimal Gross, decimal Cost, decimal Tax } GetTransaction(...) { ... }

// As a type parameter:
var transactions = new List<duck { int StoreNumber, int Transaction, DateTime Date, 
                                   decimal Gross, decimal Cost, decimal Tax }>();
2.2 鸭子类型别名

您可以在 C# 代码文件或命名空间中的命名空间 using 指令之后立即使用 using 指令为鸭子类型分配别名。然后可以使用别名代替任何类型引用。

// Namespace directives:
using System;
using Odbc = System.Data.Odbc;

// duck type aliases:
using TransTotal = duck { int StoreNumber, int Transaction, DateTime Date, 
                           decimal Gross, decimal Cost, decimal Tax };

// Member declaration:
public TransTotal GetTransaction(...) { ... }

// As a type parameter:
var transactions = new List<TransTotal>();
2.3 推断的鸭子类型

如果可以推断出属性或方法的返回类型,则可以在成员声明中省略鸭子类型文字的主体:

// Long form:
public duck { int StoreNumber, int Transaction, DateTime Date, 
              decimal Gross, decimal Cost, decimal Tax } GetDummyTransaction() {
    return new duck { ... };
}

// Short form:
public duck GetDummyTransaction() {
    return new duck { ... };
}

// Short form as a type parameter:
public IEnumerabe<duck> GetTransactions(...) {
    return 
        from record in someProvider.GetDetails(...)
        where ((DateTime)record["Date"]).Date == someDate
        group record by new {
            StoreNumber = (int)record["Number"],
            Transaction = (int)record["TransNum"],
            Date = (DateTime)record["Date"]
        } into transTotal
        select new duck {
            transTotal.Key.StoreNumber,
            transTotal.Key.Transaction,
            transTotal.Key.Date,
            Gross = transTotal.Sum(x => (decimal)x["Gross"]),
            Cost = transTotal.Sum(x => (decimal)x["Cost"]),
            Tax = transTotal.Sum(x => (decimal)x["Tax"]),
        };
}

【问题讨论】:

  • 很遗憾那里没有动态鸭子。
  • 就我个人而言,我很乐意为每个其他匿名类型编写一个自定义的不可变 POCO。我自己编写了许多复杂的 ETL 代码,相信我,使用完整的 XML cmets 将所有内容显式写出来对大脑来说更安全、更容易。
  • 这里有问题吗?这似乎应该在视觉工作室用户语音网站上,而不是在这里:visualstudio.uservoice.com
  • @ReedCopsey 是的,问题在第二行:“什么是惯用的解决方法?”但我绝对可以将提议的 duck 关键字移至 uservoice。我不熟悉它,所以谢谢你的链接。
  • 可能很傻,但我认为扭曲一些模拟框架(如 Moq)来创建一些不可变对象(通过方法或属性获取器)是合理的。这可能在本地方法范围内工作(因为匿名类型仍然是“真实”类型),但要传递它,您可能仍然必须声明不可变接口某处。到那时,您不妨自己声明不可变的 POCO。

标签: c# linq tuples anonymous-types


【解决方案1】:

ExpandoObject 您可能会感兴趣。

【讨论】:

  • 他想避免动态操作,但我同意 ExpandoObject 也是我会选择的路线。
  • @MikeBrown:哎呀,我错过了那句话。
  • @MikeBrown 这可能仍然是语义上最接近的,所以我将其标记为答案。似乎维护静态类型的共识真的是“只做该死的 POCO”。
【解决方案2】:

您似乎想要实现自己的 IDynamicObjectProvider: http://msdn.microsoft.com/en-us/library/system.dynamic.idynamicmetaobjectprovider.aspx

示例实现: http://msdn.microsoft.com/en-us/vstudio/ff800651.aspx

您似乎想要访问 List> 这样的结构,其中 String 是名称,Type 是值类型,Object 是值。

但这似乎很麻烦,可能不会提供很好的性能。您可能应该只实现您需要的所有适当的类。对于必须在您之后维护代码的人来说,为每个输入定义接口似乎是合理的。

【讨论】:

  • 关于其他人维护的好点,以及。大多数时候我都是一个人工作,但最终必须由其他人来分拆。
【解决方案3】:

您可能想看看这种方法:

public IEnumerable<T> GetTransactions<T>(..., 
    Func<int, int, DateTime, decimal, decimal, decimal, T> resultor) {
    return 
        from record in someProvider.GetDetails(...)
        where ((DateTime)record["Date"]).Date == someDate
        group record by new {
            StoreNumber = (int)record["Number"],
            Transaction = (int)record["TransNum"],
            Date = (DateTime)record["Date"]
        } into transTotal
        select resultor(
            transTotal.Key.StoreNumber,
            transTotal.Key.Transaction,
            transTotal.Key.Date,
            transTotal.Sum(x => (decimal)x["Gross"]),
            transTotal.Sum(x => (decimal)x["Cost"]),
            transTotal.Sum(x => (decimal)x["Tax"])
        );
}

resultorFunc 被映射到具体duck 的布局,采用尽可能多的参数,匹配类型,并返回T。当您调用方法并提供具体的Func 时,会推断出此T,例如:

GetTransactions(..., (sn, t, d, g, c, tx) => return new {
    StoreNumber = sn,
    Transaction = t,
    Date        = d,
    Gross       = g,
    Cost        = c,
    Tax         = tx
});

这样生成的类型在被调用方法之外可用,因为您将定义它的责任交给调用者。没有 POCO DTO,没有动态、不变性和开箱即用的平等。看看吧。

【讨论】:

    【解决方案4】:

    您确定 Dynamic 太慢了吗?我已经对动态操作与直接调用进行了性能测试,实际上对动态调用增加的影响很小感到惊讶。我没有随身携带我的号码,但found this little blog post 显示操作长度仅增加了大约十倍。他的示例显示 200 万次调用需要 85 毫秒,而静态调用需要 7 毫秒。

    确实加起来了。六秒钟的操作需要一分钟,但比处理纯反射要少得多。

    【讨论】:

    • 不使用dynamic 的类型验证和智能感知支持对我来说更加重要。我想我会听取集体建议,即生成所有必要的不可变 POCO。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-11
    • 1970-01-01
    • 1970-01-01
    • 2017-08-28
    • 1970-01-01
    相关资源
    最近更新 更多