根据呼叫位置输入推断

时间:2012-11-29 15:21:05

标签: c# generics type-inference

我想为泛型类创建一个包装函数,如下所示:

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

Create方法将作为具有签名的另一个函数的参数调用:

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

这里的目的是能够做到:

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

但是,Create无法根据预期的签名推断出自己的正确类型,因此lambda也不知道自己。

这是类型推断的限制,还是有办法使这个设置有效?

注意:如果需要,我愿意在此函数调用的某处指定实际数据类型,但我想要为每个.Create()指定它

3 个答案:

答案 0 :(得分:1)

有时您只需明确指定泛型类型参数,当c#无法推断它的实际类型时。

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

答案 1 :(得分:1)

正如其他人所解释的那样,使用您想要的确切语法是不可能的。作为解决方法,您可以将键入内容移动到单独的构建类:

    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)
        {

        }
    }

然后用法可能如下:

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

或者更接近您的示例用法(如果您希望populateFromData保留dataListView),在这种情况下您可以放弃ColumnDataBuilder<T>.populateFromData方法(因为您的评论似乎不是可以留在那里):

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

或者两全其美:

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

编辑:考虑到您的评论,您可能不希望populateFromData或甚至可能IEnumerable<T> Data存储在ColumnDataBuilder上,因此您可以简化这样做:

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

使用上面的用法:

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

答案 2 :(得分:0)

我刚提出的一个答案涉及别名。我删除了包装类并将Create方法直接移到ColumnData<T>类中,然后添加:

using ColumnData = ColumnData<MyDataType>;

这允许我使用类型提示访问编译器ColumnData.Create(),而无需在每行上指定它。我需要在每个我想要使用它的文件中创建别名,但这是一个可行的解决方案。