【问题标题】:Implicit array casting in C#C# 中的隐式数组转换
【发布时间】:2010-06-15 18:00:07
【问题描述】:

我有以下定义了隐式转换运算符的类:

class A
{
    ...
}
class B
{
    private A m_a;

    public B(A a)
    {
        this.m_a = a;
    }

    public static implicit operator B(A a)
    {
        return new B(a);
    }
}

现在,我可以将 A 隐式转换为 B。

但是为什么我不能将 A[] 隐式转换为 B[] 呢?

static void Main(string[] args)
{
    // compiles
    A a = new A();
    B b = a;

    // doesn't compile
    A[] arrA = new A[] {new A(), new A()};
    B[] arrB = arrA;
}

谢谢,马尔基。

【问题讨论】:

    标签: c# arrays operator-overloading implicit-conversion


    【解决方案1】:

    正如 Mehrdad Afshari 所提到的,您不走运地隐式执行此操作。你必须明确,它会涉及一个数组副本。值得庆幸的是,您可以使用单线来做到这一点:

    arrB = arrA.Cast<B>().ToArray();
    

    虽然如果你只想在 foreach 语句中迭代 arrB,你可以通过省略 ToArray() 来避免复制

    【讨论】:

    • Array.ConvertAll 已经涵盖了这个msdn.microsoft.com/en-us/library/exc45z53.aspx
    • @bottlenecked:好点。 ConvertAll 做和我的例子一样的事情。但是,它不允许在不复制数组的情况下将数组作为不同的类型进行迭代,而对Cast 的简单调用将允许这样做。如果需要副本,ConvertAll。如果需要迭代,Cast.
    【解决方案2】:

    数组协方差仅适用于引用类型和继承层次结构(请注意,它不是改变表示的转换:只是一组具有相同大小的指针以不同的方式解释。)它不适用于值类型和用户定义的转换。

    【讨论】:

    • @Randolpho:不正确。自 .NET 1.0 以来,数组协方差一直存在。 .NET 4 中新增的 generics 没有静态检查安全协方差。
    • 我不能写一个从 A[] 转换为 B[] 的隐式转换方法吗?我不介意必须遍历数组并将每个对象单独转换到另一个数组中。不能做吗? (我似乎找不到那个语法...)
    • @Malki:如果您不介意循环和投射/复制,请使用我建议的答案;我提到的方法会为你循环和复制。
    • @Randolpho:谢谢,这确实有帮助,但我真的在寻找一种隐含的方法......:\
    • @Malki:请记住,您的隐式运算符实际上并没有强制转换;它正在实例化一个新对象。诚然,该对象引用了原始项目,但本质上它仍然是一个浅拷贝。您最好重新考虑您的方法并使用显式演员表。
    【解决方案3】:

    全部转换

    为了明确起见,这里是您使用 ConvertAll 的方法。

    如果class B 有一个名为class A 的成员m_a,请执行以下操作:

    B[] arrB;
    A[] arrA = Array.ConvertAll(arrB, b => b.m_a);
    

    .

    如果class B有一些成员数据需要在返回A类型的对象之前进行操作(比如将一堆数值转换成字符串描述),这样做:

    class B
    {        
        public static A makeAfromB(B b)
        {
            // Do something with B data...
    
            A a = new A("data made from B data")
            return a;
        }
    
        // rest of class B implementation ...
    }
    
    // somewhere else in your code...
    A[] arrA = Array.ConvertAll(arrB, new Converter<B, A>(B.makeAfromB));
    

    .

    您还可以使用 Lambda 函数:

    A[] arrA = Array.ConvertAll(arrB, new Converter<B, A>(
        delegate(B b)
        {
            // Do something with B data, though object b is const
    
            A a = new A("data made from B data")
            return a;
        }));
    

    .

    如果 ConvertAll 可以使用隐式运算符进行转换,那就太好了,但我还没有弄清楚如何做到这一点。

    .

    演员表

    @Randolpho @瓶颈

    对于您只是想迭代并且不想制作副本的情况,使用 cast 是有意义的。但是我一直无法让它工作。

    我有一个InkPoint 类,它有一个Point 对象和一些其他成员。我想调用DrawLines(Pen, Point[]) 函数。但是,我有一个 InkPoint[] 数组。

    这是我的课程和我目前必须使用的代码,它制作了一个副本:

    public class InkPoint
    {
        public InkPoint(int x, int y)
        {
            point = new Point(x, y);
        }
    
        public Point point { get; set; }
    
        public static implicit operator Point(InkPoint p)
        {
            return p.point;
        }
    }
    
    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        InkPoint[] inkPoints = { new InkPoint(1,2), new InkPoint(3,4) };
        Point[] points = Array.ConvertAll(inkPoints, x => x.point);
    
        Pen pen = new Pen(Color.Black, 1);
        e.Graphics.DrawLines(pen, points);
    }
    

    .

    我宁愿调用它,但它不会编译,引用无效参数:

    e.Graphics.DrawLines(pen, inkPoints.Cast<Point>()); // Compile err: invalid args
    

    .

    我也尝试过迭代一个演员表,但它抛出了一个异常,理由是演员表无效

    foreach (Point p in inkPoints.Cast<Point>()) { } // Exception: cast not valid
    

    .

    我不明白为什么specified cast is not valid 因为我定义了一个隐式运算符。我可以做到以下几点:

    InkPoint ip = new InkPoint(10, 20);
    Point p1 = ip; // implicit conversion
    Point p2 = (Point)ip; // cast
    

    .

    对我来说,情况实际上比这要复杂一些。我实际上有一个 InkPoint 列表,List&lt;InkPoint&gt;,但DrawLines 函数只接受数组。所以我的代码如下所示:

    List<InkPoint> inkPoints = new List<InkPoint>();
    inkPoints.Add(new InkPoint(5, 10));
    inkPoints.Add(new InkPoint(10, 15));
    Point[] points = inkPoints.ConvertAll<Point>(x => x.point).ToArray();
    

    我可以稍微重新安排一下:

    Point[] points = Array.ConvertAll(inkPoints.ToArray(), x => x.point);
    

    .

    所以我认为这里实际上发生了两个副本。这很烦人,因为我想做的就是画线。 DrawLines 函数应该能够迭代包含对可以隐式转换为 Point 对象的对象的引用的数组/列表,这似乎不是不合理的。

    【讨论】:

    • Point 是一个结构(不是引用类型)——由于 valuetype 语义的工作方式,这意味着您的隐式转换也在制作副本。简而言之,在这种情况下您无法避免复制。即使 Point 是引用类型,Cast&lt;T&gt; 扩展也会返回 Enumerable,而不是数组,因此在这种情况下它并不是真正有用。做你想做的事情的唯一方法是重新考虑基本设计,这样你就开始存储点数组。 (InkPoint 可能是此类集合中特定元素的一种查看器。)但这需要大量工作。你真的需要它吗?
    【解决方案4】:

    想象一下,如果 Arrays 使用与 .Net 中其他集合相同的语法,而您要比较的是 Array&lt;A&gt;Array&lt;B&gt;。您不会将List&lt;A&gt;List&lt;B&gt; 进行比较。嗯,这就是你正在尝试的。

    我建议使用简单的扩展方法来获得你想要的结果,你需要稍微改变你的语法,说 'B[] arrB = arrA.ToBArray();`

    static class ArrayCast {
        public static B[] ToBArray(this A[] source) {
            var result = new B[source.Length];
            for (int i = 0;i < source.Length;i++)
                result[i] = source[i];
            return result;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-11
      • 1970-01-01
      • 2020-07-02
      • 2015-07-06
      • 1970-01-01
      相关资源
      最近更新 更多