kaalus
kaalus

Reputation: 4574

Casting of arrays in C#

In C# I am working with large arrays of value types. I want to be able to cast arrays of compatible value types, for example:

struct Color
{
    public byte R, G, B, A;
}

Color[] bitmap1 = ...;
uint[] bitmap2 = MagicCast(bitmap1);

I want bitmap2 to share memory with bitmap1 (they have the same bit representations). I don't want to make a copy.

Is there a way to do it?

Upvotes: 6

Views: 586

Answers (6)

CSharpie
CSharpie

Reputation: 9467

You can accheive this by using Structlayouts to overlay both arrays. While many answers here wrote how to make the color-struct have a uint aswell, this is about the "array" conversion:

This is evil.

public static class ColorExtension
{
    public static uint[] GetUInts(this Color[] colors)
    {
        if(colors == null) throw new ArgumentNullException("colors");
        Evil e = new Evil { Colors = colors};
        return e.UInts;
    }

    [StructLayout(LayoutKind.Explicit)]
    struct Evil
    {
        [FieldOffset(0)]
        public Color[] Colors;
        [FieldOffset(0)]
        public uint[] UInts;
    }
}

This basically has two Arrays refering the same memory Location.

So If you want to get the array of uints from the array of colors you do it like this:

Color[] colors =  ...

uint[] uints = colors.GetUInts();

As mentioned in the comments, you might need to declare the colorstruct explicitly aswell, or the runtime may fumble arround with the order leading to some "strange" uint values...

[StructLayout(LayoutKind.Sequential)] // Or explicit, but i bellieve Sequential is enough.
struct Color
{
      public byte R;

      public byte G;

      public byte B;

      public byte A;
}

Dont try to debug it though... One of the reasons why this is evil:

Upvotes: 4

dodexahedron
dodexahedron

Reputation: 4657

Aren't you essentially trying to treat your Color object as a C union?

Do this:

[StructLayout(LayoutKind.Explicit)]
struct Color
{
   [FieldOffset(0)]
   byte R;
   [FieldOffset(1)]
   byte G;
   [FieldOffset(2)]
   byte B;
   [FieldOffset(3)]
   byte A;

   [FieldOffset(0)]
   uint Value;
}

To get your IEnumerable from a Color[] without making a copy, just do:

var converted = colorArray.Select( c => c.Value );

Shouldn't make any copies, I wouldn't think, as it should just be a projection of the values into an array.

You could probably even go a step farther and add an explicit and implicit cast operator for your new type.

struct Color
{
   //...As before...

   public static implicit operator uint(Color input)
   {
      return input.Value;
   }

   public static explicit operator uint(Color input)
   {
      return input.Value;
   }
}

Then you'd be able to do something like the following:

Color a = new Color();
uint b = a;        //Completely valid, with the implicit cast.
uint c = (uint)a;  //Also valid, but the implicit cast makes it unnecessary.

This does not cover array casting, however.

Upvotes: 0

Lasse V. Karlsen
Lasse V. Karlsen

Reputation: 391296

Yes you can do this, but with the struct as posted, it may work just by chance.

You should definitely add some attributes to your struct in order to ensure there's no padding added.

Anyway, here's a LINQPad program that demonstrates:

unsafe void Main()
{
    Color[] c = new Color[2]
    {
        new Color { R = 255, G = 0, B = 0, A = 0 },
        new Color { R = 0, G = 255, B = 0, A = 0 }
    };
    c.Dump();


    fixed (byte* p = &c[0].R)
    {
        uint* i = (uint*)p;
        *i = 0x11223344;
        *(i + 1) = 0x55667788;
    };
    c.Dump();
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Color
{
    public byte R, G, B, A;
}

This will output the two colors, twice, where the second time it has been modified.

Upvotes: 1

Fredou
Fredou

Reputation: 20100

this should do what you want, you change one, it change both reference

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    [StructLayout(LayoutKind.Explicit)]
    struct Color
    {
        [FieldOffset(0)]
        public uint value;

        [FieldOffset(0)]
        public byte R;
        [FieldOffset(1)]
        public byte G;
        [FieldOffset(2)]
        public byte B;
        [FieldOffset(3)]
        public byte A;

        static public implicit operator uint(Color c)
        {
            return c.value;
        }
    }

    class Program
    {
        static unsafe void Main(string[] args)
        {
            Color[] colors = new Color[] { new Color { R = 255 }, new Color { B = 255 } };
            Console.WriteLine(colors[0]);
            Console.WriteLine(colors[1]);
            fixed (Color* thisPtr = &colors[0])
            {
                ((uint*)thisPtr)[0] = 2;
                ((uint*)thisPtr)[1] = 4;
                Console.WriteLine(colors[0]);
                Console.WriteLine(colors[1]);
            }
            Console.ReadKey();
        }
    }
}

Upvotes: 0

T_D
T_D

Reputation: 1728

It seems you want to have a different representation of the same thing. The color in 4 bytes or as one uint. This is done with unions in C. There is no native union construct in C# but functionality can be reproduced with StructLayout and FieldOffset attributes. Something like this:

using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Explicit)]
struct Color
{
  [FieldOffset(0)]
  public byte R;

  [FieldOffset(1)]
  public byte G;

  [FieldOffset(2)]
  public byte B;

  [FieldOffset(3)]
  public byte A;

  [FieldOffset(0)]
  public uint value;
}

You can then use your Color array bitmap1 to select what you need, e.g.:

var allRedBytes = bitmap1.Select(c => c.R);
var allUInts = bitmap1.Select(c => c.value);

As long as you dont call .ToArray or .ToList on that LINQ queries you dont produce a new copy of the bitmap array. These queries only describe how to walk through an array when you use the query in a foreach loop for example.

Upvotes: -1

John Alexiou
John Alexiou

Reputation: 29244

Try this:

unit[] bitmap2 = bitmap1.Select((c)=>c.ToUint32()).ToArray();

You might have to create an extension method to convert Color to uint

public static uint ToUint32(this Color color) { ... }

Upvotes: -1

Related Questions