Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# workaround for tuple with names

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 Tuples. 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):

duck (C# reference)

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.

1. duck type instances

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 };

2. duck type references

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 literals

A 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"]),
        };
}
like image 478
Joshua Honig Avatar asked Oct 22 '12 16:10

Joshua Honig


2 Answers

ExpandoObject may be of interest to you.

like image 182
Paul Ruane Avatar answered Oct 13 '22 02:10

Paul Ruane


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.

like image 21
Louis Ricci Avatar answered Oct 13 '22 01:10

Louis Ricci