Есть ли способ вернуть объект к исходному типу без указания каждого случая?

У меня есть массив объектов разных типов, и я использую BinaryWriter для преобразования каждого элемента в его двоичный эквивалент, чтобы я мог отправить структуру по сети.

Сейчас я делаю что-то вроде

for ( i=0;i<tmpArrayList.Count;i++)
{
   object x=tmpArrayList[i];
   if (x.GetType() ==  typeof(byte))
   {
      wrt.Write((byte)x);
   }
   ........

Проблема в том, что если пропустить какой-то тип, мой код может сломаться в будущем.

Хотелось бы сделать что-нибудь вроде.

object x=tmpArrayList[i];
wrt.Write(x);

но это не сработает, если я не сделаю каждый бросок.

Редактировать:

Посоветовавшись с ответами, я придумал эту функцию. Для тестирования эта функция отправляет массив в syslog.

  private void TxMsg(ArrayList TxArray,IPAddress ipaddress)
  {
     Byte[] txbuf=new Byte[0];
     int sz=0;

     // caculate size of txbuf
     foreach (Object o in TxArray)
     {
        if ( o is String ) 
        {
           sz+=((String)(o)).Length;
        }
        else if ( o is Byte[] )
        {
           sz+=((Byte[])(o)).Length;
        }
        else if ( o is Char[] )
        {
           sz+=((Char[])(o)).Length;
        }
        else // take care of non arrays
        {
           sz+=Marshal.SizeOf(o);
        }
     }
     txbuf = new Byte[sz];

     System.IO.MemoryStream stm_w = new System.IO.MemoryStream( txbuf, 0,txbuf.Length);
     System.IO.BinaryWriter wrt = new System.IO.BinaryWriter( stm_w );

     foreach (Object o in TxArray)
     {
        bool otypefound=false;
        if (o is String) // strings need to be sent one byte per char
        {
           otypefound=true;
           String st=(String)o;
           for(int i=0;i<st.Length;i++)
           {
              wrt.Write((byte)st[i]);
           }
        }
        else
        {
           foreach (MethodInfo mi in typeof(BinaryWriter).GetMethods())
           {
              if (mi.Name == "Write")
              {
                 ParameterInfo[] pi = mi.GetParameters();
                 if ((pi.Length == 1)&&(pi[0].ParameterType==o.GetType()))
                 {
                    otypefound=true;
                    mi.Invoke(wrt, new Object[] { o });
                 }
              }
           }
        }
        if(otypefound==false)
        {
           throw new InvalidOperationException("Cannot write data of type " + o.GetType().FullName);
        }
     }
     IPEndPoint endpoint = new IPEndPoint(ipaddress, 514); //syslog port
     UdpClient udpClient_txmsg = new UdpClient();
     udpClient_txmsg.Send(txbuf, txbuf.Length,endpoint); // send udp packet to syslog             
  }

person Rex Logan    schedule 11.12.2008    source источник


Ответы (6)


Вот решение для BinaryWriter, использующее отражение.

Это в основном сканирует BinaryWriter на предмет методов с именем Write, которые принимают ровно один параметр, затем строит словарь, в котором метод обрабатывает какой тип, затем для каждого объекта, который нужно записать, находит правильный метод и вызывает его на писателе.

Грязно, и вам, вероятно, следует поискать лучшие способы сделать все это (а не только письменную часть), но он должен работать для ваших текущих потребностей:

using System.IO;
using System;
using System.Reflection;
using System.Collections.Generic;
namespace ConsoleApplication14
{
    public class Program
    {
        public static void Main()
        {
            Dictionary<Type, MethodInfo> mapping = new Dictionary<Type, MethodInfo>();
            foreach (MethodInfo mi in typeof(BinaryWriter).GetMethods())
            {
                if (mi.Name == "Write")
                {
                    ParameterInfo[] pi = mi.GetParameters();
                    if (pi.Length == 1)
                        mapping[pi[0].ParameterType] = mi;
                }
            }

            List<Object> someData = new List<Object>();
            someData.Add((Byte)10);
            someData.Add((Int32)10);
            someData.Add((Double)10);
            someData.Add((Char)10);
            someData.Add("Test");

            using (FileStream file = new FileStream(@"C:\test.dat", FileMode.Create, FileAccess.ReadWrite))
            using (BinaryWriter writer = new BinaryWriter(file))
            {
                foreach (Object o in someData)
                {
                    MethodInfo mi;
                    if (mapping.TryGetValue(o.GetType(), out mi))
                    {
                        mi.Invoke(writer, new Object[] { o });
                    }
                    else
                        throw new InvalidOperationException("Cannot write data of type " + o.GetType().FullName);
                }
            }
        }
    }
}
person Lasse V. Karlsen    schedule 11.12.2008
comment
Однажды у меня была такая же проблема, и я сделал это по-вашему с первой попытки, но это было слишком медленно. Переход на лестницу if-else на самом деле будет значительно быстрее! - person Autodidact; 19.02.2009

Нет. Приведение должно быть известно во время компиляции, но фактический тип известен только во время выполнения.

Обратите внимание, однако, что есть лучший способ тестирования типа, вызывающего GetType. Вместо того:

if (x.GetType() == typeof(byte))

Использовать:

if (x is byte)

РЕДАКТИРОВАТЬ: Чтобы ответить на дополнительные вопросы:

"Какие бывают типы?" Что ж, я думаю, посмотрите документацию по BinaryWriter ...

"Мне нужно беспокоиться о байтах и ​​байтах?" Нет, byte - это псевдоним System.Byte в C #. Они одного типа.

person Jon Skeet    schedule 11.12.2008
comment
+1 для просмотра документации для BinaryWriter ... обратите внимание, что есть перегрузки для массивов - Write (char []) и Write (byte []) - person Jimmy; 11.12.2008
comment
Почему для сравнения типов лучше использовать Is лучше, чем GetType ()? - person sthay; 21.12.2008

Джон прав, но у меня была еще одна мысль, которая может вам пригодиться. Рассматривали ли вы добавление еще одного байта к передаче каждого объекта, а затем использование этого байта в качестве кода типа, сообщая вам, к чему его привести на другом конце?

person Ian Jacobs    schedule 11.12.2008

Рассматривали ли вы использование BinaryFormatter вместо BinaryWriter?

Преимущества

  • Вы можете передать object (т.е. что угодно), так что это решит вашу проблему приведения типов.
  • Автоматическое управление типами (фактически записывает заголовки типов в поток).
  • Также поддерживает сложные ссылочные типы.

Недостатки

Внутренне использует сериализацию, поэтому:

  • Наверное, медленнее.
  • Поток байтов увеличивается (из-за заголовков типов).
  • У вас нет контроля над форматом байтов, поэтому это не вариант в сценариях взаимодействия.
  • Возможные проблемы с версией (совместимость между разными версиями сборок сериализованного типа).
  • Требуется разрешение на доступ к коду сериализации (актуально в сценариях частичного доверия).
person Christoph Rüegg    schedule 11.12.2008
comment
Мне нужен более жесткий контроль над сгенерированными пакетами, но я могу использовать их в будущем. - person Rex Logan; 11.12.2008

Вы просите Dynamic Dispatch, а в C # 3.0 его нет.

Вы должны, по крайней мере, использовать проверку во время выполнения, чтобы убедиться, что вы не пропустили тип.

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

person Jay Bazuzi    schedule 11.12.2008
comment
Не могу дождаться C # 4.0. Возможно, вам стоит показать ему пример того, как это можно сделать в C # 4.0 с помощью ключевого слова dynamic? - person Llyle; 11.12.2008
comment
Не знаю, можно ли это сделать в C # 4.0. Фактически, я подозреваю, что OP требуется двойная динамическая отправка, и что C # 4.0 добавит только одиночную динамическую отправку. Но я не уверен. - person Jay Bazuzi; 14.12.2008

Это случай, когда требуется нечто, называемое Double Dispatch.

Я собираюсь предположить, что объект wrt - это тот, который вы написали сами (допустим, он имеет тип Writer). Вот что можно было сделать:

class Writer
{
    void write(byte b)
    {
        // write bytes here
    }

    void write(Writable something)
    {
        something.writeOn(this);
    }
}

interface Writeable
{
    void writeOn(Writer writer);
}

class SomeObject implements Writeable
{
    private Object someData;
    private Object moreData;

    void writeOn(Writer writer)
    {
        writer.write(convertToByte(someData));
        writer.write(convertToByte(moreData));
    }
}

class AnotherObject implements Writeable
{
    private int x;
    private int y;
    private int z;

    void writeOn(Writer writer)
    {
        writer.write((byte)x);
        writer.write((byte)y);
        writer.write((byte)z);
    }
}

Что делает Writer, так это отправляет обратно на вход, говоря ему использовать его (Writer) для записи, однако это делается для этого объекта, который не может быть известен заранее.

person moffdub    schedule 11.12.2008