views:

101

answers:

3

I'm writing an app in C# (.net 3.5) and I have a question about class design:

I'd like to create a class which accesses a file (read, write) and provides its content to the users (instanciators) of the class. The most common operation on an instance will be to retrieve a certain value from the file. The actual read and write (io) operations are faily expensive so I'd like to keep the file data in memory and let all instances access this data. The class is located in an assembly that is used from various applications simultaniously, so I guess I should be worrying about thread safety.

How do I design this with respect to thread-safety and unit-testability (for unit-tests, different inputfiles must be used than in operational code)? Any help is greatly appreciated.

+1  A: 

Firstly, make your class implement an appropriate interface. That way, clients can test their behaviour without needing real files at all.

Testing the thread safety is hard - I've never seen anything which is really useful on that front, though that's not to say the tools aren't out there.

For unit testing your class, I'd suggest that if possible it should work with a general stream rather than just a file. Then you can embed different test files in your test assembly, and refer to them with GetManifestResourceStream. I've done this several times in the past, with great success.

Jon Skeet
Could you please provide some more details? I still don't know how to design the class. What should the interface look like? Only the GetFilePortionX / SetFilePortionY? How can I avoid having the filedata loaded again with each instaciation. Bear with me, I'm a begginer and really stuck on this.
Matthias
Ah, I hadn't spotted that you needed write as well as read access. That's a pain in terms of testing - although you can use a MemoryStream to back it. As for the interface, work out what the clients need. Is Get/SetPortion all? If so, that's your interface.
Jon Skeet
A: 

Regarding thread safety: Thread safety is not an issue unless multiple threads within a single application will be referring to the same instance of your class concurrently. Unless your class is contained within an out of process server, there is no way for multiple applications to refer to the same instance concurrently. Therefore, the conflicts you're likely to see will come from file sharing violations rather than threading issues (in other words, different instances of the class attempting to read and write the same file). And yes, you must design your code to deal with file sharing appropriately.

One way to make the class unit testable is to provide the class with a stream in the constructor rather than having the class access a file directly. Then the unit test can supply a memory stream, for example, instead of supplying a file stream.

Craig Stuntz
ad thread-safety: does that mean, even if I would make the class static, the various applications would not get the same static object?ad testability: I thought about your approach, but then I would need to read the data from the file each time an instance is created which I would like to avoid.
Matthias
Separate AppDomains (or applications) never share instances. The only way to share an instance is with an out of process server.
Craig Stuntz
+1  A: 

Use ReaderWriterLock, which I think fits the problem description.

Following is a quick and dirty implementation. Acquiring locks could be smarter, like trying multiple times before bailout etc. But you get the point:

public class MyFooBarClass
{
   private static ReaderWriterLock readerWriterLock = new ReaderWriterLock();
   private static MemoryStream fileMemoryStream;

   // other instance members here

   public void MyFooBarClass()
   {
     if(fileMemoryStream != null)
     {
     // probably expensive file read here
     }

     // initialize instance members here
   }

   public byte[] ReadBytes()
   {
    try
    {
     try
      {
      readerWriterLock.AcquireReaderLock(1000);
      //... read bytes here
      return bytesRead;
      }
      finally
      {
      readerWriterLock.ReleaseReaderLock();
      }
     }
     catch(System.ApplicationException ex)
     {
     System.Diagnostics.Debug.WriteLine(ex.Message);
     }
   }

   public void WriteBytes(bytes[] bytesToWrite)
   {
    try
    {
     try
      {
      readerWriterLock.AcquireWriterLock(1000);
      //... write bytes here
      }
      finally
      {
      readerWriterLock.ReleaseWriterLock();
      }
     }
     catch(System.ApplicationException ex)
     {
     System.Diagnostics.Debug.WriteLine(ex.Message);
     }
   }
}
Vivek