Возникли проблемы с методами расширения для байтовых массивов - PullRequest
5 голосов
/ 05 апреля 2010

Я работаю с устройством, которое отправляет обратно изображение, и когда я запрашиваю изображение, перед данными изображения появляется некоторая недокументированная информация. Я смог понять это, только просматривая двоичные данные и идентифицируя информацию заголовка изображения внутри.

У меня изначально был обычный метод, и я преобразовал его в метод расширения. Первоначальный вопрос был связан с тем, что компилятор жаловался на то, что в качестве первого параметра не указан 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 ]

5 голосов
/ 05 апреля 2010

Методы расширения - это статические методы, которые кажутся только методами экземпляра. Вы можете считать экземпляр, над которым работает метод расширения, доступным только для чтения (по значению). Назначение экземпляра метода 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;
    }
}

Надеюсь, это поможет.

EDIT: Вышеприведенное верно только для типов значений. Если тип, который вы расширяете, является ссылочным типом, то у вас не возникнет проблемы, работающей непосредственно с типом, который вы пытаетесь сделать выше. К сожалению, байтовый массив является структурой и, следовательно, является производным от 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 голос
/ 05 апреля 2010

Мне жаль, что я не знаю, с какой конкретной проблемой вы сталкиваетесь или как ее решить [конечно, проверка ссылок на пространство имен и разрешение любых конфликтов с помощью методов с аналогичными именами - это начало], но я заметил одну или две странности .

Рассмотрим следующий пример решения,

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>.

Если вы действительно заинтересованы в "передаче по ссылке", на месте, семантике побочных эффектов, то один из вариантов может быть следующим

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. При определенных порогах это просто манипулирование базовым массивом и \ или создание новых массивов для нас.

Надеюсь, это поможет! :)

...