I want immutable anonymous types with named members that can be passed around, compared and recognized -- a merging of Tuples and anonymous types. This doesn't exist, and I realize that.
So the question is: What is a good idiomatic substitute for this using C#4 or 5?
The use case is fluid LINQ data processing from heterogeneous data sources. In a word, ETL in C#. I do some pretty complex analysis and data comes from multiple platforms and sources. It's not an option to "just put it all on the same platform and use Entity Framework". I want to able to fluidly pass around what are essentially arbitrary records -- immutable named sets of read only properties.
The only thing I can come up with short of creating a custom immutable POCO for every single otherwise-anonymous type is to use attributes to add compiled annotations to methods which return Tuple
s. Certainly it wouldn't be hard to write a code-generator for spitting out the immutable POCOs, but I hate how that clutters up the object model of a project. Using dynamic
completely erases all the performance and design-time usefulness of static typing, especially if composing further queries from the results of other methods, so I don't consider it a viable solution.
// 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 { ... }
What I want (Imaginary MSDN documentaion for C# 6):
The duck keyword allows anonymous types to be used in all the statically-typed features of C#. Like normal anonymous types, the compiler will treat anonymous types with the same number, names, and types of properties as having the same type. However, the duck keyword also allows these types to be used in member declarations and as type parameters for generic types.
Like anonymous types, instances of duck type objects can only be created using an object initializer without a type name. The syntax is the same as for a normal anonymous type except that the keyword duck is added after the new operator:
var record = new duck { StoreNumber=1204,
Transaction=410,
Date=new DateTime(2012, 12, 13),
Gross=135.12m,
Cost=97.80m,
Tax=12.11m };
Duck types can be referenced using a duck type literal, a duck type alias, or implicitly when the return type of a property or method can be inferred.
2.1 duck type literalsA duck type can be expressed with a type literal, which can be used in the place of any type reference. Duck type literals consist of the keyword duck followed by a list of name - type identifier pairs just as in a parameter list of a method, except enclosed in curly braces:
// 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 duck type aliases
You can assign an alias to a duck type with a using directive immediately after the namespace using directives in a C# code file or namespace. The alias may then be used in the place of any type reference.
// 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 Inferred duck types
If the return type of a property or method can be inferred, the body of a duck type literal may be omitted in a member declaration:
// 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"]),
};
}
ExpandoObject may be of interest to you.
Seems like you'd want to implement your own IDynamicObjectProvider: http://msdn.microsoft.com/en-us/library/system.dynamic.idynamicmetaobjectprovider.aspx
Example implementation: http://msdn.microsoft.com/en-us/vstudio/ff800651.aspx
You seem to want to access a structure like List> where String is the name, Type is the value type and Object is the value.
BUT that seems like a big hassle and probably wouldn't offer very good performance. You should probably just implement all the proper classes you need. For the sanity of whoever has to maintain the code after you, defining interfaces for each input just seems rationale.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With