views:

1471

answers:

6

I have an array of different type objects and I use a BinaryWriter to convert each item to its binary equivalent so I can send the structure over the network.

I currently do something like

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

The problem is that if miss a type them my code might break in the future.

I would like to do something like.

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

but it doesn't work unless I do each cast.

Edit:

After consulting the answers this is what I came up with for the function. For testing this function sends the array to 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             
  }
+6  A: 

No. The cast has to be known at compile-time, but the actual type is only known at execution time.

Note, however, that there's a better way of testing the type calling GetType. Instead of:

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

Use:

if (x is byte)

EDIT: To answer the extra questions:

"What are all the types?" Well, look down the docs for BinaryWriter, I guess...

"Do I need to worry about byte and Byte?" No, byte is an alias for System.Byte in C#. They're the same type.

Jon Skeet
+1 for "look down the docs for BinaryWriter"... note there are overloads for arrays -- Write(char[]) and Write(byte[])
Jimmy
Why is using Is better than GetType() for type comparison?
sthay
It's faster, the code is simpler, and it's null-safe.
Jon Skeet
+3  A: 

Jon's right, but I had another thought that you may find useful. Have you considered adding in another byte to the transmission of each object, then using that byte as a type code, telling you what to cast it to on the other end?

Ian Jacobs
+1  A: 

What you're asking for is Dynamic Dispatch, and C# 3.0 doesn't have it.

You should at least use a runtime check to verify that you aren't missing a type.

You may be able to do something clever where you have a Dictionary that maps from types to processing functions. You can fill in the mapping for all processing functions in one place. You have a better chance of getting this right than if you write a switch wherever the processing happens.

Jay Bazuzi
Cant wait for C#4.0. You should perhaps show him an example of how it can be done in C#4.0 using the dynamic keyword?
vanslly
I don't know if it can be done in C#4.0. In fact, I suspect that what the OP needs is *double dynamic dispatch*, and that C# 4.0 will only add *single dynamic dispatch*. But I'm not certain.
Jay Bazuzi
+1  A: 

This is a case of needing something called Double Dispatch.

I'm going to assume that the wrt object is one you wrote yourself (Let's say it is of type Writer). Here is what you could do:

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);
    }
}

What Writer is doing is dispatching back to the input, telling it to use it (the Writer) to write, however that is done for that object, which cannot be known ahead of time.

moffdub
+3  A: 

Have you considered using a BinaryFormatter instead of the BinaryWriter?

Advantages

  • You can pass objects (i.e. anything), so it solves your casting problem.
  • Automatic type management (actually writes type headers to the stream).
  • Supports complex reference types as well.

Disadvantages

Uses Serialization internally, therefore:

  • Probably slower.
  • Byte stream gets larger (because of the type headers).
  • You don't have control over the byte format, therefore not an option in interop scenarios.
  • Potential version issues (compatibility between different assembly versions of the serialized type).
  • Requires the serialization code access permission (relevant in partial trust scenarios).
Christoph Rüegg
I need tighter control over packet generated but might use in the future.
Rex Logan
+2  A: 

Here is a solution for BinaryWriter that uses reflection.

This basically scans BinaryWriter for methods named Write that takes exactly one parameter, then builds a dictionary of which method handles which type, then for each object to write, finds the right method and calls it on the writer.

Dirty, and you should probably look for better ways of doing the whole thing (not just the writing part), but it should work for your current needs:

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);
                }
            }
        }
    }
}
Lasse V. Karlsen
I once had the same problem and I did it your way for the first attempt but it was too slow. Changing this to an if-else ladder will actually be significantly faster!
SDX2000