Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type inference based on calling location

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().

like image 384
Bobson Avatar asked Jul 18 '26 18:07

Bobson


2 Answers

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()));
like image 82
Chris Sinclair Avatar answered Jul 20 '26 08:07

Chris Sinclair


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());
like image 29
Olivier Jacot-Descombes Avatar answered Jul 20 '26 07:07

Olivier Jacot-Descombes