10

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

所以问题是:使用 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# 参考)

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

1.鸭式实例

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

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类型引用

可以使用鸭子类型字面量、鸭子类型别名来引用鸭子类型,或者在可以推断出属性或方法的返回类型时隐式地引用鸭子类型。

2.1 鸭子类型字面量

鸭子类型可以用类型文字表示,它可以用来代替任何类型引用。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"]),
        };
}
4

4 回答 4

6

您可能对ExpandoObject感兴趣。

于 2012-10-22T16:31:02.747 回答
1

似乎您想实现自己的 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 是值。

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

于 2012-10-22T18:04:35.313 回答
1

您可能想看看这种方法:

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"])
        );
}

resultor Func映射到您的具体布局,duck采用与它一样多的参数,具有匹配的类型,并返回一个T. T当您调用方法广告提供您的具体时,就会推断出这Func一点,例如:

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

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

于 2012-10-22T18:44:09.457 回答
0

你确定动态太慢了吗?我已经对动态操作与直接调用进行了性能测试,实际上对动态调用增加的影响很小感到惊讶。我没有随身携带数字,但发现这篇小博客文章显示操作长度仅增加了大约十倍。他的示例显示 200 万次调用需要 85 毫秒,而静态调用需要 7 毫秒。

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

于 2012-10-22T19:46:45.643 回答