Implicit array casting in C#

2019-05-04 19:50发布

问题:

I have the following classes with an implicit cast operator defined:

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

Now, I can implicitly cast A to B.

But why can't I implicitly cast A[] to 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;
}

Thanks, Malki.

回答1:

As Mehrdad Afshari mentioned, you're out of luck doing this implicitly. You'll have to get explicit, and it'll involve an array copy. Thankfully, you can probably do it with a one-liner:

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

Although if you only want to iterate arrB in a foreach statement, you can avoid the copy by omitting ToArray()



回答2:

Array covariance only works for reference types and in the inheritance hierarchy (note that it's not a representation-changing conversion: just a set of pointers with identical size interpreted differently.) It will not work for value types and user defined conversions.



回答3:

ConvertAll

Just to be explicit, here is how you use ConvertAll.

In the case where class B has a member of class A called m_a, do this:

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

.

If class B has some member data that you need to manipulate before you can return an object of type A (such as turning a bunch of numerical values into a string description), do this:

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

.

You can also use Lambda functions:

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

.

It would be nice if ConvertAll could use the implicit operator to do the conversion, but I haven't figured out how to do that.

.

Cast

@Randolpho @bottlenecked

For the cases where you simply want to iterate and would prefer not to make a copy, using cast makes sense. However I have been unable to make it work.

I have an InkPoint class which has a Point object and some other members. I want to call the DrawLines(Pen, Point[]) function. However, I have an array of InkPoint[].

Here is my class and the code I currently have to use, which makes a copy:

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

.

I would rather call this, but it won't compile, citing invalid arguments:

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

.

I've also tried iterating over a cast, but it throws an exception, citing the cast is not valid

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

.

I don't understand why the specified cast is not valid since I've defined an implicit operator. I'm able to do the following just fine:

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

.

For me, the situation is actually slightly more complicated than that. I actually have a list of InkPoints, List<InkPoint>, but the DrawLines function accepts only arrays. So my code looks like this:

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

I can rearrange it slightly to this:

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

.

So I think there's actually two copies happening here. This is annoying since all I want to do is draw the lines. It doesn't seem unreasonable that the DrawLines function should be able to iterate over some array/list that contains references to objects that can be implicitly converted to Point objects.



回答4:

Imagine for a moment if Arrays used the same syntax as other collections in .Net, and what you're trying to compare is an Array<A> with an Array<B>. You wouldn't compare a List<A> to a List<B>. Well, that's essentially what you're trying.

I'd recommend using a simple extension method to get the result you want, you'll need to change your syntax slightly to say '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;
    }
}