views:

143

answers:

3

Think of the following code:

static int Main() {
     byte[] data = File.ReadAllBytes("anyfile");
     SomeMethod(data);
     ...
}
static void SomeMethod(byte[] data) {
     data[0] = anybytevalue; // this line should not be possible!!!
     byte b = data[0];       // only reading should be allowed
     ...
}

Is there a way of readonly passing the byte[] in C#? Copying isn't a solution. I would not like to waste memory (because the file might get very big). Please keep performance in mind!

+8  A: 

You can pass a ReadOnlyCollection<byte>, like this:

static int Main() {
     byte[] data = File.ReadAllBytes("anyfile");
     SomeMethod(new ReadOnlyCollection<byte>(data));
     ...
}
static void SomeMethod(ReadOnlyCollection<byte> data) {
     byte b = data[0];       // only reading is allowed
     ...
}

However, it would be better to pass a Stream, like this:
This way, you won't read the entire file into memory at all.

static int Main() {
     Stream file = File.OpenRead("anyfile");
     SomeMethod(file);
     ...
}
static void SomeMethod(Stream data) {
     byte b = data.ReadByte();       // only reading is allowed
     ...
}
SLaks
Are there any performance differences when using the ReadOnlyCollection<byte> instead of the array?
youllknow
@youllknow: Not measurably; `ReadOnlyCollection` is a thin wrapper around an `IList<T>`.
SLaks
@youllknow: As SLaks mentioned, ReadOnlyCollection<T> is actually a wrapper around IList<T>. So accessing an item is a method call *and* an interface call. This is very expensive, compared to the cost of accessing a single byte in an array (like 20x more expensive). If your array is large, be sure to profile this!
nikie
@nikie, I did some tests, on average it's about 3.5 times slower, not 20 times...
Thomas Levesque
@Thomas: What did you test? I tested summing a 1.000.000 byte array in a loop. Array: 0.6ms, ReadOnlyCollection: 20 ms. That's a lot more than factor 3.5
nikie
I did the same with a 1GB array. I just tried again with a 1MB array, I still get a ratio of about 3.5.
Thomas Levesque
@nikie, @Thomas: What bitness are you testing on?
SLaks
@SLaks: Windows 7, 32 bit, Core i7 processor. Do you think that could lead to a difference that large?
nikie
@nikie:The CLR and JITters are very different for different bitnesses. (Somewhat less so in .Net 4.0)
SLaks
Thanks very much, do performance reason I will pass the original byte[] instead of getting more code security!
youllknow
+2  A: 

I would recommend you using the highest possible object in the hierarchy that does the job. In your case this would be IEnumerable<byte>:

static int Main() 
{
     byte[] data = File.ReadAllBytes("anyfile");
     SomeMethod(data);
}

static void SomeMethod(IEnumerable<byte> data)
{
    byte b = data.ElementAt(0); 
    // Notice that the ElementAt extension method is sufficiently intelligent
    // to use the indexer in this case instead of creating an enumerator
}
Darin Dimitrov
`((byte[])data)[0] = anybytevalue;`
SLaks
@Slaks, sure casting, but no matter what you try there will always be ways to circumvent this. For example reflection. So even with a `ReadOnlyCollection<byte>` the user will be capable of modifying the original byte structure (though he will need to write a bit more code than simply casting). I assumed that the OP was looking for a compile time safety here and not runtime.
Darin Dimitrov
@Darin: Well, you *could* create a copy of the data with Enumerable.ToList. No way to circumvent that with a cast or reflection. I wouldn't recommend it, though.
nikie
+1  A: 

I think this might be what you're looking for.

Compile the code below and you will get this compile error: Property or indexer 'Stack2.MyReadOnlyBytes.this[int]' cannot be assigned to -- it is read only

public class MyReadOnlyBytes
{
   private byte[] myData;

   public MyReadOnlyBytes(byte[] data)
   {
      myData = data;
   }

   public byte this[int i]
   {
      get
      {
         return myData[i];
      }
   }
}

class Program
{
   static void Main(string[] args)
   {
      var b = File.ReadAllBytes(@"C:\Windows\explorer.exe");
      var myb = new MyReadOnlyBytes(b);

      Test(myb);

      Console.ReadLine();
   }

   private static void Test(MyReadOnlyBytes myb)
   {
      Console.WriteLine(myb[0]);
      myb[0] = myb[1];
      Console.WriteLine(myb[0]);
   }
}
Tyler Jensen
nice solution too! have you done any speed comparisions?
youllknow
have not done any speed comps
Tyler Jensen