【问题标题】:C# - Accept IEnumerable of any type as parameterC# - 接受任何类型的 IEnumerable 作为参数
【发布时间】:2021-10-18 18:49:09
【问题描述】:

我有这个构造函数,它应该接受任何类型的任何 IEnumerable,并尝试将值转换为所需的类型

public ChartData(IEnumerable<dynamic> labels, IEnumerable<dynamic> values)
            {
                this.values = values.Select(x=>(float)Convert.ToString(x));
                this.labels = labels.Select(x=>(string)Convert.ToString(x));
            }

当我调用这个构造函数时,我可以传递字符串类型的 ienumerables,但不能传递 int 或 float 如何创建一个可以接受任何类型的 ienumerables 而不会产生大量重载的构造函数?

【问题讨论】:

  • 存在用于编译时类型检查的类型。动态的——甚至对象——关闭编译时类型检查。 | Co-和Conravairance是一种选择。但通常您会将 ChartData 设为通用,因此您可以决定放入 IEnumerable 的类型。
  • 查找通用方法以了解如何以正确的方式执行此操作:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
  • 如果您接受任何类型,也包括IEnumerable&lt;User&gt; 和 ``IEnumerable`,您希望如何将它们转换为浮点数和字符串?最好有一个封装浮点数和字符串值的对象,为此使用接口。
  • 这里要记住的一点是,由于本地化/文化方面的考虑,将值与字符串相互转换非常缓慢且容易出错。这是要避免的。因此,如果我们有一个IEnumerable&lt;double&gt;,并且需要将其转换为IEnumerable&lt;float&gt;,则将每个双精度数转换为一个字符串,然后将这些字符串中的每一个转换回一个浮点数可能是处理这个问题的最慢和最不可靠的方法.

标签: c# dynamic casting


【解决方案1】:

如果您接受任何类型,也包括IEnumerable&lt;User&gt;IEnumerable&lt;Car&gt;,您希望如何将它们转换为浮点数和字符串?拥有一个封装浮点数和字符串值的对象会更好,为此使用接口。例如,有一个这样的界面:

public interface IChartItem
{
    float Value { get; set; }
    string Label { get; set; }
}

一个示例类:

public class Foo : IChartItem
{
    public int MyProperty { get; set; }
    public string Name { get; set; }
    
    public float Value => (float)MyProperty;
    public string Label => Name;
}

现在你的函数可以是带有约束的泛型:

public void ChartData<T>(IEnumerable<T> items)
    where T : IChartItem
{
    this.values = items.Select(x => x.Value);
    this.labels = items.Select(x => x.Label);
}

备选方案

您还可以传入您的对象以及几个Func 参数来获取值和标签。例如:

public void ChartData<T>(IEnumerable<T> items, 
    Func<T, float> valueSelector, 
    Func<T, string> labelSelector)
{
    this.values = items.Select(x => valueSelector(x));
    this.labels = items.Select(x => labelSelector(x));
}

并像这样使用它:

ChartData(foos, f => (float)f.MyProperty, f => f.Name);

【讨论】:

    【解决方案2】:

    如果您不关心类型,请使用非泛型版本IEnumerable(这是IEnumerble&lt;T&gt; 的基本接口):

    public ChartData(IEnumerable labels, IEnumerable values)
    {
          // x is of type Object for both lines.
          this.values = values.Select(x=>(float)Convert.ToSingle(x));
          this.labels = labels.Select(x=>(string)Convert.ToString(x));
    }
    

    注意

    • 您将失去所有类型安全性 - 有人可以轻松地将文件列表或其他一些随机类型作为参数之一传递
    • 如果由于装箱成本而处理值类型,一般来说泛型版本更可取。幸运的是,在您的情况下,无论如何您都需要装箱值或Convert

    就我个人而言,我只接受IEnumerable&lt;float&gt;(或者可能是double)和IEnumerable&lt;String&gt;作为参数——这个方法的调用者将有更多的知识来选择所需的字段/转换,而不是我的代码试图猜测他们传递的类型如果某些“任何数字类型”是所需的输入,我会检查 Is there a constraint that restricts my generic method to numeric types? 并实现所有变体,而不是使用 dynamic 回退到运行时反射或使用非泛型版本缺乏类型安全。

    【讨论】:

    • “我个人只接受 IEnumerable&lt;float&gt;IEnumerable&lt;string&gt; 作为参数”。 完全正确。调用者将知道他们拥有的类型,并且能够更好地处理转换。 Cast&lt;&gt;()Select&lt;&gt;() 方法让他们很容易做到这一点。在问题中通过string 往返所有内容是最慢和最不可靠的方法。
    • @JoelCoehoorn 的一个问题是它对不匹配数量的元素开放。例如 10 个值和 11 个标签。
    • @DavidG 哦,当然。基于索引匹配集合很少是一个好方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多