带有名称的元组的C#解决方法

时间:2012-10-22 16:18:05

标签: c# linq tuples anonymous-types

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

所以问题是:使用C#4或5,这是一个很好的惯用替代品吗?

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

我唯一能够为每个单独的匿名类型创建自定义不可变POCO的方法是使用属性将已编译的注释添加到返回Tuple s的方法中。当然,编写用于吐出不可变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文档):

duck(C#reference)

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

1。鸭子类型实例

与匿名类型一样,只能使用对象初始值设定项创建duck类型对象的实例 没有类型名称。除了关键字之外,语法与普通匿名类型的语法相同 在运算符后添加 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类型可以使用duck类型文字,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指令为鸭类型分配别名。然后可以使用别名代替任何类型的引用。

// 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推断的鸭子类型

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

// 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 个答案:

答案 0 :(得分:6)

您可能会对

ExpandoObject感兴趣。

答案 1 :(得分:1)

好像你想要实现自己的IDynamicObjectProvider: http://msdn.microsoft.com/en-us/library/system.dynamic.idynamicmetaobjectprovider.aspx

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

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

但这似乎是一个很大的麻烦,可能不会提供非常好的表现。您应该只需要实现所需的所有类。为了理解那些必须在你之后维护代码的人,为每个输入定义接口似乎是合理的。

答案 2 :(得分: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,例如:{/ p>

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

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

答案 3 :(得分:0)

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

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