views:

110

answers:

3

I'm trying to create a generic extension method, that works on typed data tables :

public static class Extensions
{
    public static TableType DoSomething<TableType, RowType>(this TableType table, param Expression<Func<RowType, bool>>[] predicates)
        where TableType : TypedTableBase<RowType>
        where RowType : DataRow
    {
        // do something to each row of the table where the row matches the predicates
        return table;
    }

    [STAThread]
    public static void main()
    {
        MyTypedDataSet.MyTypedDataTable table = getDefaultTable();
    }

    public static MyTypedDataSet.MyTypedDataTable getDefaultTable()
    {
        // this line compiles fine and does what I want:
        return new MyTypedDataSet.MyTypedDataTable().DoSomething<MyTypedDataSet.MyTypedDataTable, MyTypedDataSet.MyTypedRow>(row => row.Field1 == "foo");

        // this line doesn't compile :
        return new MyTypedDataSet.MyTypedDataTable().DoSomething(row => row.Field1 == "foo");
        // Error : The type arguments .. cannot be inferred from the usage
    }
}

The first line works fine, but it's really ugly...
The second line doesn't compile because the compiler cannot infer the type of RowType.
This is a method that will be used as part of a DataLayer by many different programmers, so I would rather not need them to specify the TypeParameter.
Shouldn't the compiler know that RowType is the same type as the one that was used by TypedTableBase ?

For different reasons that may not be obvious in this code sample, I really need to return the datatable in its original form. And the reason I need RowType is so the 'Expression<Func<T, bool>>' will be typed and seen by InteliSence.

Thanks

+5  A: 

Method type inference does not make inferences from arguments to constraints. It makes inferences from arguments to formal parameters and then checks whether the inferences made from the arguments to the formals satisfy the constraints.

In your case there is not enough data from the arguments to deduce what the type parameters are without first looking at the constraints, which we're not going to do until we check the inferences against the constraints. Sorry about that, but that's how the type inference algorithm is specified.

I've been asked questions about this many times and the consensus seems to be that I am morally wrong for maintaining the position that inference should infer from arguments to formal parameters alone. For about a dozen people telling me I'm wrongheaded in this regard, see the comments to my analysis of this closely related issue:

http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

I maintain my position.

Eric Lippert
I was afraid of that... thanks for the answer
Pierre-Olivier Goulet
@Eric: You are right :)
Brian
A: 

Eric's answer is great for explaining why the types cannot be inferred. Here are a couple of suggestions to hopefully cut down on the verbosity of the code that you will have to write.

If you can explicitly define the type of your lambda expression, then it can infer the types.

One example of how to do that is below. I've created a criteria parameter that is explicitly of type Expression<Func<MyTypedDataSet.MyTypedRow, bool>>. In this example, this doesn't save you much typing, but perhaps in practice you can make use of this.

        MyTypedDataSet.MyTypedDataTable table = new MyTypedDataSet.MyTypedDataTable();

        Expression<Func<MyTypedDataSet.MyTypedRow, bool>> criteria = row => row.Field1 == "foo";

        return table.DoSomething(criteria);

EDIT: altered my example to use another extension method rather than deriving a custom TypedTableBase<T> class from System.Data.TypedTableBase<T>.

Below is another example that can do a better job of inferring the type parameters. You define another extension method (mine is called RowPredicate) that only has one type parameter to infer. The first parameter is of type TypedTableBase<RowType>, so the compiler should have no problem inferring the type from that:

    public static Expression<Func<RowType, bool>> RowPredicate<RowType>(this TypedTableBase<RowType> table, Expression<Func<RowType, bool>> predicate)
        where RowType : DataRow
    {
        return predicate;
    }

This allows you to compile the following code:

        MyTypedDataSet.MyTypedDataTable table = new MyTypedDataSet.MyTypedDataTable();

        return table.DoSomething(table.RowPredicate(row => row.Field1 == "foo"));

Primarily the table parameter simply servers to inform the compiler of the type to use for RowType. Is this a good idea? I'm not so sure, but it does allow the compiler to infer all of the generic types.

Dr. Wily's Apprentice
A: 

Even if that wasn't ideal, I gave up trying to return anything at all witch allows me to do something like that :

public static void DoSomething<RowType>(this TypedTableBase<RowType> table, param Expression<Func<RowType, bool>>[] predicates)
    where RowType : DataRow
    {
        // do something to each row of the table where the row matches the predicates
        // do not return the table... too bad for chaining commands
    }

And then use it like so:

MyTypedDataSet.MyTypedDataTable table = new MyTypedDataSet.MyTypedDataTable();
table.DoSomething(row => row.Field1 == "foo"));

and the compiler infers the type correctly...

Thank you both for your answers.

Pierre-Olivier Goulet