使用字节数组的扩展方法时遇到问题

时间:2010-04-05 13:58:43

标签: c# extension-methods bytearray

我正在使用发回图像的设备,当我请求图像时,图像数据之前会有一些未记录的信息。我只能通过查看二进制数据并识别内部的图像标题信息来实现这一点。

我最初使用普通方法并将其转换为扩展方法。这里的原始问题与编译器抱怨没有将Array作为第一个参数(我有Byte [])有关,但事实证明我犯了错误而忘记删除调用代码中的第一个参数。换句话说,我曾经有过:

Byte[] new_buffer = RemoveUpToByteArray(buffer, new byte[] { 0x42, 0x4D });

并且在更改为扩展方法后,我错误地使用了:

buffer.RemoveUpToByteArray( buffer, new byte[] { 0x42, 0x4D });

无论如何,现在这一切都已修复,因为我在将代码示例输入SO时意识到了我的错误。 然而,我遇到了一个新问题,即只是缺乏对扩展方法和参考与值类型的理解。这是代码:

public static void RemoveFromByteArrayUntil(this Byte[] array, Byte[] until)
{
    Debug.Assert(until.Count() > 0);
    int num_header_bytes = until.Count();
    int header_start_pos = 0; // the position of the header bytes, defined by [until]
    byte first_header_byte = until[0];
    while(header_start_pos != -1) {
        header_start_pos = Array.IndexOf(array, first_header_byte, header_start_pos);
        if(header_start_pos == -1)
            break;
        // if we get here, then we've found the first header byte, and we need to look
        // for the next ones sequentially
        for(int header_ctr=1; header_ctr<num_header_bytes; header_ctr++) {
            // we're going to loop over each of the header bytes, but will
            // bail out of this loop if there isn't a match
            if(array[header_start_pos + header_ctr] != until[header_ctr]) {
                // no match, so bail out.  but before doing that, advance
                // header_start_pos so the outer loop won't find the same
                // occurrence of the first header byte over and over again
                header_start_pos++;
                break;
            }
        }
        // if we get here, we've found the header!
        // create a new byte array of the new size
        int new_size = array.Count() - header_start_pos;
        byte[] output_array = new byte[new_size];
        Array.Copy(array, header_start_pos, output_array, 0, new_size);
        // here is my problem -- I want to change what array points to, but
        // when this code returns, array goes back to its original value, which
        // leads me to believe that the first argument is passed by value.
        array = output_array;
        return;
    }
    // if we get here, we didn't find a header, so throw an exception
    throw new HeaderNotInByteArrayException();
}

我现在的问题是看起来扩展方法的第一个参数是按值传递的。我想重新分配哪些数组指向,但在这种情况下,看起来我只需要操纵数组的数据。

2 个答案:

答案 0 :(得分:5)

扩展方法是静态方法,它们只是实例方法。您可以将扩展方法正在处理的实例视为只读(按值)。分配作为扩展的第一个参数的byte []的实例方法将不起作用。你将无法摆脱分配,但你可以修改你的扩展,然后像这样编写你的作业:

buffer = buffer.RemoveUpToByteArray(header);

使您的扩展返回字节数组结果,并且不要尝试分配扩展名内的缓冲区。你的扩展将是这样的:

public static class MyExtensionMethods
{
    public static byte[] RemoveUpToByteArray(this byte[] buffer, byte[] header)
    {
        byte[] result = buffer;

        // your logic to remove header from result

        return result;
    }
}

我希望这会有所帮助。

编辑: 以上内容仅适用于值类型。如果您要扩展的类型是引用类型,那么您不会像上面尝试的那样直接对该类型进行操作。遗憾的是,字节数组是一个结构,因此派生自System.ValueType。考虑以下内容,这在扩展中是完全合法的,并且会给出期望的结果:

public class MyBytes
{
    public byte[] ByteArray { get; set; }
}

public static class MyExtensionMethods
{
    // Notice the void return here...
    public static void MyClassExtension(this MyBytes buffer, byte[] header)
    {
        buffer.ByteArray = header;
    }
}

答案 1 :(得分:1)

对不起,我不知道你遇到了什么具体问题,或者如何解决它[当然检查命名空间被引用并解决与类似命名方法的任何冲突是一个开始],但我确实注意到一两个怪异

考虑以下示例解决方案,

using System.Linq;
namespace Sample.Extensions
{
    public static class ByteExtensions
    {
        public static void RemoveHeader (this byte[] buffer, byte[] header)
        {
            // take first sequence of bytes, compare to header, if header
            // is present, return only content
            // 
            // NOTE: Take, SequenceEqual, and Skip are standard Linq extensions
            if (buffer.Take (header.Length).SequenceEqual (header))
            {
                buffer = buffer.Skip (header.Length).ToArray ();
            }
        }
    }
}

这在VS2010RC中编译并运行。为了演示用法,

using Sample.Extensions;
namespace Sample
{
    class Program
    {
        static void Main (string[] args)
        {
            byte[] buffer = new byte[] { 00, 01, 02 };
            byte[] header = new byte[] { 00, 01 };
            buffer.RemoveHeader (header);

            // hm, so everything compiles and runs, but buffer == { 00, 01, 02 }
        }
    }
}

因此我们不会收到编译或运行时错误,但很明显它不会按预期运行。这是因为扩展必须仍然符合标准方法语义,这意味着参数是按值传递的。我们无法将buffer更改为指向我们的新数组。

我们可以通过将我们的方法重写为常规函数语义来解决这个问题,

public static byte[] RemoveHeaderFunction (this byte[] buffer, byte[] header)
{
    byte[] stripped = null;
    if (stripped.Take (header.Length).SequenceEqual (header))
    {
        stripped = stripped.Skip (header.Length).ToArray ();
    }
    else
    {
        stripped = buffer.ToArray ();
    }
    return stripped;
}

现在

using Sample.Extensions;
namespace Sample
{
    class Program
    {
        static void Main (string[] args)
        {
            byte[] buffer = new byte[] { 00, 01, 02 };
            byte[] header = new byte[] { 00, 01 };

            // old way, buffer will still contain { 00, 01, 02 }
            buffer.RemoveHeader (header);

            // new way! as a function, we obtain new array of { 02 }
            byte[] stripped = buffer.RemoveHeaderFunction (header);
        }
    }
}

不幸的是,数组是不可变的值类型[可能错误地使用这些术语]。修改“数组”的唯一方法是将容器更改为可变引用类型,如List<byte>

如果你真的热衷于“通过ref”,就地,副作用语义,那么一个选项可能是以下

using System.Linq;
namespace Sample.Extensions
{
    public static class ListExtensions
    {
        public static void RemoveHeader<T> (this List<T> list, List<T> header)
        {
            if (list.Take (header.Count).SequenceEqual (header))
            {
                list.RemoveRange (0, header.Count);
            }
        }
    }
}

至于用法,

static void Main (string[] args)
{
    byte[] buffer = new byte[] { 00, 01, 02 };
    byte[] header = new byte[] { 00, 01 };

    List<byte> bufferList = buffer.ToList ();

    // in-place side-effect header removal
    bufferList.RemoveHeader (header.ToList ());
}

在幕后,List<T>正在维护一个T类型的数组。在某些阈值,它只是操纵底层数组和\或为我们实例化新数组。

希望这有帮助! :)