【问题标题】:Having trouble with extension methods for byte arrays字节数组的扩展方法有问题
【发布时间】:2011-02-04 10:38:30
【问题描述】:

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

我原来有一个普通的方法,然后将它转换为扩展方法。这里的原始问题与编译器抱怨没有 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();
}

我现在的问题是,扩展方法的第一个 this 参数似乎是按值传递的。我想重新分配数组指向的内容,但在这种情况下,我似乎只需要操作数组的数据。

【问题讨论】:

  • 不是答案,只是观察:您的 RemoveUpToByteArray 似乎每次调用时都必须创建一个新的字节数组。使用整数作为数组的索引来跟踪您已阅读的距离会更有效。
  • @Dave:您对编译器错误发生的位置并不太具体。我猜它在 call site 而不是扩展方法。即使是这样,您也没有显示任何代码。您需要充实这一点才能得到答案,因为您可以在字节数组上创建扩展方法并轻松执行它们。
  • 好的,没问题!我会继续发布 real 代码。 :) 感谢您的反馈。
  • @casperOne:实际上我写错了这个问题,我会在调用者和被调用者(扩展方法)中有错误。我会尽快发布所有内容。
  • 您的扩展方法是否与调用代码具有相同的命名空间?如果没有,是否引用了该命名空间?

标签: c# extension-methods bytearray


【解决方案1】:

扩展方法是静态方法,看起来只是实例方法。您可以认为扩展方法正在处理的实例是只读的(按值)。分配给作为扩展的第一个参数的 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;
    }
}

【讨论】:

  • 我有另一个我更喜欢的解决方案......我现在将它放在我的问题中,请随时发表评论。
  • 我收回这一点,我认为你是对的,我没有其他选择。
  • 在您已经创建的类上定义扩展方法有什么意义? MYBytes 中的一个方法可以完成这项工作。
【解决方案2】:

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

考虑以下示例解决方案,

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&lt;byte&gt;

如果您真的热衷于“通过 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&lt;T&gt; 维护着一个 T 类型的数组。在某些阈值下,它只是简单地操作底层数组和\或为我们实例化新数组。

希望这会有所帮助! :)

【讨论】:

  • 谢谢,约翰尼。 Audie 得出了同样的结论……我希望可以通过第一个参数作为参考。例如,为什么我不能将 'ref' 与 'this' 一起使用?
猜你喜欢
  • 2011-05-28
  • 2016-06-07
  • 1970-01-01
  • 2012-04-20
  • 1970-01-01
  • 2010-10-12
  • 1970-01-01
  • 1970-01-01
  • 2016-07-30
相关资源
最近更新 更多