3

I want to create a wrapper function for a generic class like so:

public class ColumnData
{
    public static ColumnData<T> Create<T>(string name, int width, ColumnType type,
                                          Func<T, string> dataFormater)
    {
        return new ColumnData<T>(name, width, type, dataFormater);
    }
}

The Create method will be called as an argument to another function with a signature:

public void populateFromData<TDATA>(IEnumerable<TDATA> data, 
                                    params ColumnData<TDATA>[] columns)   
{
   ...
}

The intent here is to be able to do:

var myData = new List<MyDataType>();
dataListView.populateFromData(
    myData,
    ColumnData.Create("ID", 40, ColumnType.Numeric, x => x.ID.ToString());

However, Create can't infer the correct type for itself based on the signature it's expected to have, and thus the lambda doesn't know itself either.

Is this a limitation of type inference, or is there a way to make this setup work?

Note: I'm willing to specify the actual data type somewhere in this function call, if necessary, but I don't want to specify it for each .Create().

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
Bobson
  • 13,498
  • 5
  • 55
  • 80
  • 1
    I think this _is_ a limitation - `myData` tells you that `populateFromData`'s generic type is ``, but that doesn't _force_ a generic type on `ColumnData.Create`; rather `ColumnData.Create` is responsible for its _own_ generic type, and _then_ the compiler checks to see if there's a `populateFromData` overload that matches. That's not to say there isn't a way to achieve what you want some other way, though... – Rawling Nov 29 '12 at 15:33
  • Couldn't you provide the type parameter through ˜dataListView˜ – Jahan Zinedine Nov 29 '12 at 15:36
  • @Rawling - That makes a lot of sense. Any suggestions as to another way? – Bobson Nov 29 '12 at 15:36
  • @Jani - No, because `dataListView` is a designer object (it inherits from ListView), so it can't be generic. I could explicitly specify the type on the `populateFromData()` call, but that doesn't make any difference. – Bobson Nov 29 '12 at 15:38
  • This is a similar question asked by myself http://stackoverflow.com/questions/4477636/why-must-i-provide-explicitly-generic-parameter-types-while-the-compiler-should which refer to generic implementation limitations. – Jahan Zinedine Nov 29 '12 at 15:59

3 Answers3

1

Sometimes you just have to specify the generic type parameter explicitly, when c# cannot infer it's actual type.

dataListView.populateFromData(
    myData,
    ColumnData.Create<MyDataType>("ID", 40, ColumnType.Numeric, x => x.ID.ToString());
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • That's what I was afraid of. When the datatype is something like `ShippingReceiptHeader.PendingLineItemDetails`, it gets rather painful. – Bobson Nov 29 '12 at 15:32
1

As others have explained, it's not possible with the exact syntax you want. As a workaround, you could possibly move the typing to a separate building class:

    public class ColumnDataBuilder
    {
        public static ColumnDataBuilder<T> ColumnsFor<T>(IEnumerable<T> data)
        {
            return new ColumnDataBuilder<T>(data);
        }
    }

    public class ColumnDataBuilder<T> : ColumnDataBuilder
    {
        public IEnumerable<T> Data { get; private set; }

        public ColumnDataBuilder(IEnumerable<T> data)
        {
            this.Data = data;
        }
        public ColumnData<T> Create(string name, int width, ColumnType type, Func<T, string> dataFormater)
        {
            return new ColumnData<T>(name, width, type, dataFormater);
        }

        public void populateFromData(params ColumnData<T>[] columns)
        {
            ///...
        }
    }

    public class ColumnData<T>
    {
        public ColumnData(string name, int width, ColumnType type, Func<T, string> dataFormatter)
        {

        }
    }

Then usage might look like:

        var builder = ColumnDataBuilder.ColumnsFor(new List<MyDataType>());
        builder.populateFromData(builder.Create("ID", 40, ColumnType.Numeric, x => x.ID.ToString()));
        IEnumerable<MyDataType> data = builder.Data;

Or closer to your example usage (if you want to keep populateFromData on your dataListView) in which case you can ditch the ColumnDataBuilder<T>.populateFromData method (since it seems from your comments that's not possible to keep there):

        var myData = new List<MyDataType>();
        var builder = ColumnDataBuilder.ColumnsFor(myData);
        dataListView.populateFromData(myData, builder.Create("ID", 40, ColumnType.Numeric, x => x.ID.ToString()));

Or a bit of best of both worlds:

        var builder = ColumnDataBuilder.ColumnsFor(new List<MyDataType>());
        dataListView.populateFromData(builder.Data, builder.Create("ID", 40, ColumnType.Numeric, x => x.ID.ToString()));

EDIT: Considering your comments, you probably don't want populateFromData or possibly even the IEnumerable<T> Data stored on the ColumnDataBuilder, so you might simplify to have this instead:

    public class ColumnDataBuilder<T> : ColumnDataBuilder
    {
        public ColumnData<T> Create(string name, int width, ColumnType type, Func<T, string> dataFormater)
        {
            return new ColumnData<T>(name, width, type, dataFormater);
        }
    }

    public class ColumnDataBuilder
    {
        public static ColumnDataBuilder<T> ColumnsFor<T>(IEnumerable<T> data)
        {
            return new ColumnDataBuilder<T>();
        }
    }

With the usage from above:

        var myData = new List<MyDataType>();
        var builder = ColumnDataBuilder.ColumnsFor(myData);
        dataListView.populateFromData(myData, builder.Create("ID", 40, ColumnType.Numeric, x => x.ID.ToString()));
Chris Sinclair
  • 22,858
  • 3
  • 52
  • 93
  • Oooh, that's really clever. I just posted my own answer, but I think I'll switch to this, since it's more generically useful and doesn't involve remembering to alias (and what the alias means). – Bobson Nov 29 '12 at 15:55
  • 1
    Given the last usage scenario, I was able to boil this down even further. I moved `ColumnsFor` to the class for `dataListView`, and kept the `ColumnDataBuilder` class, but removed everything except the `Create` function. The rest was extraneous. – Bobson Nov 29 '12 at 16:04
0

One answer I just came up with involves an alias. I removed the wrapper class and moved the Create method into the ColumnData<T> class directly, then added:

using ColumnData = ColumnData<MyDataType>;

This allows me to access ColumnData.Create() with the type hint to the compiler, without needing to specify it on each line. I'll need to create the alias in each file where I want to use this, but it is a workable solution.

Bobson
  • 13,498
  • 5
  • 55
  • 80