czwartek, 9 października 2008

Is it possible to write c# application without using cast?

For last few months I'm more and more fascinated with .NET generics. Especially interesting for me is a (totally academic) question: Is it possible to write application without using an cast?
While considering that option first 2 problematic cases I found was some kind of cache or ServiceProvider implementations. I even asked a question on stackoverflow.com if it's possible, but the only answer I had was that it's not or that I should have something like WeakDictionary - which probably will use cast in its implementation.

But I wouldn't be oneself if I will abandon this subject :-). As I showed in question it's possible for only one instance using static generic fields, but I can't find the solution for non static objects. After few days I think I found the solution to this - not so elegant, but it should work.

So here you have fully generic (but simplified) implementation of ServiceProvider:

    6 public class ServiceContainer : IDisposable

    7 {

    8     readonly IList<IService> services = new List<IService>();

    9 

   10     public void Add<T>(T service)

   11     {

   12         Add<T,T>(service);

   13     }

   14 

   15     public void Add<Key, T>(T service) where T : Key

   16     {

   17         services.Add(new Service<Key>(this, service));

   18     }

   19 

   20     public void Dispose()

   21     {

   22         foreach(var service in services)

   23             service.Remove(this);

   24     }

   25 

   26     ~ServiceContainer()

   27     {

   28         Dispose();

   29     }

   30 

   31     public T Get<T>()

   32     {

   33         return Service<T>.Get(this);

   34     }

   35 }

   36 

   37 public interface IService

   38 {

   39     void Remove(object parent);

   40 }

   41 

   42 public class Service<T> : IService

   43 {

   44     static readonly Dictionary<object, T> services = new Dictionary<object, T>();

   45 

   46     public Service(object parent, T service)

   47     {

   48         services.Add(parent, service);

   49     }

   50 

   51     public void Remove(object parent)

   52     {

   53         services.Remove(parent);

   54     }

   55 

   56     public static T Get(object parent)

   57     {

   58         return services[parent];

   59     }

   60 }

As you can see I store all services in static generic Dictionary, which also have information to which serviceProvider this service belong. Normally when ServiceProvider will be cleaned by Garbage Collector, all services also will be removed from memory. But because I have static field that holds them, to avoid memory leaks I'm cleaning those services in destructor/finalizer o ServiceProvider.

I see two drawbacks of this solution

  • Larger memory usage due to need of having two lists of references to those services
  • ServiceProvider stays one generation longer in memory than normal solution with cast, because of finalizer usage.

Now with this example Implementation of cache without cast shouldn't be so hard :-)

LATE EDIT (2009-07-30): As someone may notice from my answer on stackoverflow.com  I tried to implement such ServiceProvider but failed to do it without memory leaks. Uhh :-(

My request to microsoft: Please implement WeakReference<T>.

środa, 2 lipca 2008

One instance of dataset in application and Windows Forms designer

Want to know one simple requirement that makes Windows Forms designer useles?
Here it is:

Application should use one instance of Dataset (or other component) in all windows and user controls.

Simple enough, right. So let's see it on some simple example: application that have two forms - list and the details of concrete record.
So first the list form. I've dragged the dataset table from the datasource window (in VS 2008) and it creates automagically Dataset object, datasource and gridview with apropriate databindings. Pretty amazing, huh.
Now lets go to the details form. The same way I've dragged the table but choosed that I want to see details. The same story: Dataset object, datasource and fields.
Now it seems that because we have two separate datasets in each form, the Microsoft way to fill this datasets is to fill them twice from the database (which cost bandwith),  or once from the database - on the list form - and on the details form just copy it (which costs your developement time needed for code you must write). The situation is worse if each line on the list (let say customers) have many related tables that are also needed (for example are aggregated and shown in Dataset expression columns) - you must reload/copy them all.
Another stupid effect of this default behaviour of designer is that you must first call InitilizeComponent() which binds all controls to the created empty dataset and only then you can fill/copy data making great cascade of unnecesary events that will refresh the controls and which could be avoided if we will have ability to provide already filled Dataset and then the code could databind.

The simplest solution to this problem would be to force the designer in some way to use only one instance of Dataset in all windows.
Three years ago, when I for the first time spot this problem, the only solution that came to my mind was to abandon the designer...

OK I'm joking

The only solution that I've found then was to create a method that goes through all components and rebinds all components to the new Dataset (just after InitilizeComponent()). It was ugly hack that had similiar performance like copying all needed data to the new already binded Dataset, but doesn't duplicated the data in memory.
This way I used it for three years and it somehow works.
During this time I've found that InstanceDescriptor (object that defines what code to produce in InitilizeComponent() to create your component) allows to use static methods. My happines was cooled down so fast when I saw that code generates correctly, but it makes designer to crash. I cannot find the reason for two days and I droped this path.

In the beggining of this year I owned Visual Studio 2008 and tried to write one application using WPF. I saw that it allows to use DynamicResource which behave exactly like my rebinding in windows forms. I've tried also to force it to use static method to create object, but again failed and I must say that WPF for me seems less extensive than windows forms (read -  more sealed and interal, or better - even runtime control tree has Sealed flag - lol).
I'm not convinced to WPF. Comparing to Windows Forms you must assume that you will have no designer. Nor Expression Blend, nor VS 2008 designer works for me. It ends for me that I designed only in XAML and the only preview was runtime, because designer didn't worked. For my luck this project was skipped for some time.

Right now I'm sitting on a new project back in Windows Forms and it's so much easier :-). But this time I had some free time and enough patience to sit down and find the reason why InstanceDescriptor generating static method blows away designer (of course this bug was not fixed in VS 2008 :-( ).
So in short time I've created typed Dataset called DB and my class called Data that will inherit from it to use InstanceDescriptor

[TypeConverter(typeof(OneInstanceTypeConverter<Data>))]
    [
FactoryMethod(typeof(Data), "Instance")]
   
public class Data : DB
    {
       
static readonly Data instance = new Data();
       
public static Data Instance()
        {
           
return instance;
        }
    }

   
public class OneInstanceTypeConverter<T> : TypeConverter
    {
       
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
           
if (destinationType == typeof(InstanceDescriptor))
               
return true;
           
TypeConverter converter =
                (
TypeConverter) typeof (T).BaseType.GetCustomAttributes(typeof (TypeConverter), false)[0];
           
return converter.CanConvertTo(context, destinationType);
        }

       
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
           
if (destinationType == typeof (InstanceDescriptor) && value is T)
            {
               
object[] attributes = typeof (T).GetCustomAttributes(typeof (FactoryMethodAttribute), false);
               
if (attributes.Length == 0)
                   
return new InstanceDescriptor(typeof (T).GetConstructor(new Type[0]), null);
               
return new InstanceDescriptor(((FactoryMethodAttribute)attributes[0]).Method, null, true);
            }
           
TypeConverter converter =
                (
TypeConverter)typeof(T).BaseType.GetCustomAttributes(typeof(TypeConverter), false)[0];
           
return converter.ConvertTo(context, culture, value, destinationType);
        }        
    }

    [
AttributeUsage(AttributeTargets.Class)]
   
public class FactoryMethodAttribute : Attribute
    {
       
private readonly MethodInfo method;

       
public FactoryMethodAttribute(Type type, string method)
        {
           
this.method = type.GetMethod(method, BindingFlags.Public | BindingFlags.Static);
        }

       
public MethodInfo Method
        {
           
get { return method; }
        }
    }

This code produces following line in InitilizeComponent():

this.data = Data.Instance();

Sweet, but when you close the form and try to open it again it will show you few exceptions that happens somwhere in CodeDomSerializer.Deserialize and says that field 'data' does not exists.
So serialization to static method works, but deserialization can't create object from it, and this is the reason I've abandoned it earlier.
Right now after two nights with Reflector and VS Debugger (of course for some reason VS 2008 didn't wanted to cooperate and load System.Design source code to debugger) I finally spot the place that have problem. But first... some theory.
When you drag some object onto form, it's created in memory and added to the designer list of objects which should be serialized to code. Later when designer is opening the file it deserializes it and calls appropriate methods to create it. usually it's a constructor, but we are trying here to call static method. What we see here is that VS can create object from constructor, but have some problem when it must call the static method. So coming this path I've checked if static method is called at all. To my lucky it was. Her is the stack trace:

HRDB.exe!Data.Instance() Line 23    C#
[Native to Managed Transition]    
[Managed to Native Transition]    
System.Design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializerBase.DeserializeExpression(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager, string name, System.CodeDom.CodeExpression expression) + 0xc25 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializer.DeserializeStatementToInstance(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager, System.CodeDom.CodeStatement statement) + 0x6b bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializer.Deserialize(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager = {System.ComponentModel.Design.Serialization.DesignerSerializationManager}, object codeObject) + 0x114 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.TypeCodeDomSerializer.DeserializeName(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager = {System.ComponentModel.Design.Serialization.DesignerSerializationManager}, string name = "data", System.CodeDom.CodeStatementCollection statements) + 0x300 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.TypeCodeDomSerializer.Deserialize(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager = {System.ComponentModel.Design.Serialization.DesignerSerializationManager}, System.CodeDom.CodeTypeDeclaration declaration = {System.CodeDom.CodeTypeDeclaration}) + 0xe00 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.PerformLoad(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager) + 0x56 bytes    
Microsoft.VisualStudio.Design.dll!Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.PerformLoad(System.ComponentModel.Design.Serialization.IDesignerSerializationManager serializationManager) + 0x2c4 bytes    
Microsoft.VisualStudio.Design.dll!Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.DeferredLoadHandler.Microsoft.VisualStudio.TextManager.Interop.IVsTextBufferDataEvents.OnLoadCompleted(int fReload) + 0x48 bytes    
[Native to Managed Transition]   

I tried to find the bug somwhere in the code near the one stack trace shows me, but without success. And as a one of my ideas, I tried to comapre it to normal constructor.

HRDB.exe!HRDB.Data.Data() Line 18    C#
[Native to Managed Transition]    
[Managed to Native Transition]    
System.dll!System.SecurityUtils.SecureConstructorInvoke(System.Type type, System.Type[] argTypes, object[] args, bool allowNonPublic, System.Reflection.BindingFlags extraFlags) Line 153 + 0xc bytes    C#
System.dll!System.ComponentModel.ReflectTypeDescriptionProvider.CreateInstance(System.IServiceProvider provider, System.Type objectType = {Name = "Data" FullName = "HRDB.Data"}, System.Type[] argTypes, object[] args = {object[0]}) Line 198 + 0x11 bytes    C#
System.dll!System.ComponentModel.TypeDescriptor.TypeDescriptionNode.CreateInstance(System.IServiceProvider provider, System.Type objectType, System.Type[] argTypes, object[] args) Line 3751 + 0x10 bytes    C#
System.dll!System.ComponentModel.TypeDescriptionProvider.CreateInstance(System.IServiceProvider provider, System.Type objectType, System.Type[] argTypes, object[] args) Line 70 + 0xfffffff1 bytes    C#
System.dll!System.ComponentModel.TypeDescriptor.TypeDescriptionNode.CreateInstance(System.IServiceProvider provider, System.Type objectType, System.Type[] argTypes, object[] args) Line 3751 + 0x10 bytes    C#
System.dll!System.ComponentModel.TypeDescriptor.CreateInstance(System.IServiceProvider provider, System.Type objectType, System.Type[] argTypes, object[] args) Line 600 + 0x18 bytes    C#
System.Design.dll!System.ComponentModel.Design.DesignSurface.CreateInstance(System.Type type = {Name = "Data" FullName = "HRDB.Data"}) + 0x60 bytes    
Microsoft.VisualStudio.Design.dll!Microsoft.VisualStudio.Design.VSDesignSurface.CreateInstance(System.Type type) + 0x20 bytes    
System.Design.dll!System.ComponentModel.Design.DesignerHost.System.ComponentModel.Design.IDesignerHost.CreateComponent(System.Type componentType = {Name = "Data" FullName = "HRDB.Data"}, string name = "data") + 0xcf bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.DesignerSerializationManager.CreateInstance(System.Type type = {Name = "Data" FullName = "HRDB.Data"}, System.Collections.ICollection arguments, string name = "data", bool addToContainer = true) + 0x1fb bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.DesignerSerializationManager.System.ComponentModel.Design.Serialization.IDesignerSerializationManager.CreateInstance(System.Type type, System.Collections.ICollection arguments, string name = "data", bool addToContainer) + 0x94 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializerBase.DeserializeInstance(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager, System.Type type, object[] parameters, string name, bool addToContainer) + 0x24 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.ComponentCodeDomSerializer.DeserializeInstance(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager = {System.ComponentModel.Design.Serialization.DesignerSerializationManager}, System.Type type, object[] parameters, string name, bool addToContainer) + 0x23 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializerBase.DeserializeExpression(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager, string name, System.CodeDom.CodeExpression expression) + 0x550 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializer.DeserializeStatementToInstance(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager, System.CodeDom.CodeStatement statement) + 0x6b bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.CodeDomSerializer.Deserialize(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager = {System.ComponentModel.Design.Serialization.DesignerSerializationManager}, object codeObject) + 0x114 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.TypeCodeDomSerializer.DeserializeName(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager = {System.ComponentModel.Design.Serialization.DesignerSerializationManager}, string name = "data", System.CodeDom.CodeStatementCollection statements) + 0x300 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.TypeCodeDomSerializer.Deserialize(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager = {System.ComponentModel.Design.Serialization.DesignerSerializationManager}, System.CodeDom.CodeTypeDeclaration declaration = {System.CodeDom.CodeTypeDeclaration}) + 0xe00 bytes    
System.Design.dll!System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.PerformLoad(System.ComponentModel.Design.Serialization.IDesignerSerializationManager manager) + 0x56 bytes    
Microsoft.VisualStudio.Design.dll!Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.PerformLoad(System.ComponentModel.Design.Serialization.IDesignerSerializationManager serializationManager) + 0x2c4 bytes    
Microsoft.VisualStudio.Design.dll!Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.DeferredLoadHandler.Microsoft.VisualStudio.TextManager.Interop.IVsTextBufferDataEvents.OnLoadCompleted(int fReload) + 0x48 bytes    
[Native to Managed Transition]    

This one is a longer one but it shows that for normal contructor there is much more done than for static method. The paths seems to break in CodeDomSerializerBase.DeserializeExpression() method. For static method it calls the metod, but for constructor it calls ComponentCodeDomSerializer.DeserializeInstance().
Another night and I knew everything.

CodeDomSerializerBase.DeserializeExpression(IDesignerSerializationManager manager, string name, CodeExpression expression);

For static method expression parameter is CodeMethodInvokeExpression which results in just one call to System.Type.InvokeMember().
For constructor, expresion parameter is CodeObjectCreateExpression which results in call to ComponentCodeDomSerializer.DeserializeInstance() which is calling IDesignerSerializationManager.CreateInstance which apart from calling constructor is also adding created object to the list of components so it can be later used when it's refrenced in other CodeStatement.

So I've found a bug, but how to overcome it. It takes me a little time to find a way to do it without using Reflection. I was forced to create my own serializer which will exchange the CodeMethodInvokeExpression to CodeObjectCreateExpression just before deserialization. Below you have the full code:

    [DesignerSerializer(typeof(OneInstanceSerializer<Data>), typeof(CodeDomSerializer))]
    [
TypeConverter(typeof(OneInstanceTypeConverter<Data>))]
    [
FactoryMethod(typeof(Data), "Instance")]
   
public class Data : DB
    {
       
static readonly Data instance = new Data();
       
public static Data Instance()
        {
           
return instance;
        }
    }

   
public class OneInstanceTypeConverter<T> : TypeConverter
    {
       
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
           
if (destinationType == typeof(InstanceDescriptor))
               
return true;
           
TypeConverter converter =
                (
TypeConverter) typeof (T).BaseType.GetCustomAttributes(typeof (TypeConverter), false)[0];
           
return converter.CanConvertTo(context, destinationType);
        }

       
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
           
if (destinationType == typeof (InstanceDescriptor) && value is T)
            {
               
object[] attributes = typeof (T).GetCustomAttributes(typeof (FactoryMethodAttribute), false);
               
if (attributes.Length == 0)
                   
return new InstanceDescriptor(typeof (T).GetConstructor(new Type[0]), null);
               
return new InstanceDescriptor(((FactoryMethodAttribute)attributes[0]).Method, null, true);
            }
           
TypeConverter converter =
                (
TypeConverter)typeof(T).BaseType.GetCustomAttributes(typeof(TypeConverter), false)[0];
           
return converter.ConvertTo(context, culture, value, destinationType);
        }        
    }

    [
AttributeUsage(AttributeTargets.Class)]
   
public class FactoryMethodAttribute : Attribute
    {
       
private readonly MethodInfo method;

       
public FactoryMethodAttribute(Type type, string method)
        {
           
this.method = type.GetMethod(method, BindingFlags.Public | BindingFlags.Static);
        }

       
public MethodInfo Method
        {
           
get { return method; }
        }
    }

   
public class OneInstanceSerializer<T> : CodeDomSerializer
    {
       
public override object Deserialize(IDesignerSerializationManager manager, object codeObject)
        {            
           
CodeDomSerializer serializer = GetSerializer(manager, typeof (T).BaseType);
           
CodeStatementCollection statements = codeObject as CodeStatementCollection;
           
if (statements != null)
            {
               
for (int i = 0; i < statements.Count; i++)
                {
                   
object value = DeserializeStatementToInstance(manager, statements[i]);

                   
if (value != null)
                    {
                       
CodeAssignStatement statement = statements[i] as CodeAssignStatement;
                       
if(statement != null)
                            statement.Right =
new CodeObjectCreateExpression(typeof (T));
                       
break;
                    }
                }
               
return serializer.Deserialize(manager, statements);
            }

           
return serializer.Deserialize(manager, codeObject);
        }

       
public override object Serialize(IDesignerSerializationManager manager, object value)
        {
           
return GetSerializer(manager, typeof(T).BaseType).Serialize(manager, value);
        }
    }

It's written in a way that you can just copy it and use in your project to any component you want. Just remember that you must use all 3 attributes.
I hope that it will help someone to write more performant code and still use designer to design the window in an elegant way.

I must say that - in my opinion - it's sad that windows forms style of designing will be abandon in favour of WPF and XAML. Just try to do such thing in WPF.
Wait I have better one, try to inherit from BaseForm class in WPF.
And my favourite - try to write equivalent of following generated code in WPF:

this.components = new MyContainer(this);

The key here is keyword this. I don't know how to pass Form reference in XAML. In Windows Forms it was not easy to do it, but it's possible (I'll show you later how). BTW I needed it to inject my IServiceProvider into each Form and its controls/components.

poniedziałek, 19 maja 2008

How to test static method call - Benchmarks

This is a continuation of my last post.

For a long time I rejected to even test Delegates as a method to make static method calls "virtual" because of performance. My mind doesn't allow me to accept the fact that they can be viable alternative.

But real numbers was a little strange.

First, test console program

class Program : IProgram
    {
       
private static Action TestStaticDelegate = TestStatic;
       
private static Action TestStaticNoInlineDelegate = TestStaticNoInline;

       
static void Main(string[] args)
        {
            TestStatic();
            TestStaticNoInline();
           
IProgram p = new Program();
           
Action TestInterfaceDelegate = p.TestInterface;
            p.TestInterface();

           
int number = 1000000000;
            
           
DateTime start = DateTime.Now;
           
for (int i = 0; i < number; i++ )
                TestStatic();
           
Console.WriteLine("TestStatic: " + (DateTime.Now - start).Ticks);

            start =
DateTime.Now;
           
for (int i = 0; i < number; i++)
                TestStaticNoInline();
           
Console.WriteLine("TestStaticNoInline: " + (DateTime.Now - start).Ticks);

            start =
DateTime.Now;
           
for (int i = 0; i < number; i++)
                p.TestInterface();
           
Console.WriteLine("TestInterface: " + (DateTime.Now - start).Ticks);

            start =
DateTime.Now;
           
for (int i = 0; i < number; i++)
                TestStaticDelegate();
           
Console.WriteLine("TestStaticDelegate: " + (DateTime.Now - start).Ticks);

            start =
DateTime.Now;
           
for (int i = 0; i < number; i++)
                TestStaticNoInlineDelegate();
           
Console.WriteLine("TestStaticNoInlineDelegate: " + (DateTime.Now - start).Ticks);

            start =
DateTime.Now;
           
for (int i = 0; i < number; i++)
                TestInterfaceDelegate();
           
Console.WriteLine("TestInterfaceDelegate: " + (DateTime.Now - start).Ticks);
        }

       
private static int x = 0;

       
static void TestStatic()
        {
            x++;
        }

       
private static int y = 0;
        [
MethodImpl(MethodImplOptions.NoInlining)]
       
static void TestStaticNoInline()
        {
            y++;
        }

       
private int z = 0;
       
public void TestInterface()
        {
            z++;
        }        
    }

   
public interface IProgram
    {
       
void TestInterface();
    }

As you can see I want to compare static method calls with delegates and (the most important) with virtual call.
I've run this program on my AMD Athlon XP 2400+ 2GHz machine (it was Release version of course :-). The results were following:

TestStatic: 18326352
TestStaticNoInline: 39556880
TestInterface: 56280928
TestStaticDelegate: 67697344
TestStaticNoInlineDelegate: 61989136
TestInterfaceDelegate: 56180784


For me the most intresting numbers are TestInterface and TestStaticDelegate, because it compares Dependency Injection using interface with DI using Delegates. As you can see delegates are around 17% slower then interface method call. That was predictable but I've suspected much higher difference. I've run this test few times and the results was always similiar.



According to the power this method gives me, it's acceptable performance penalty, but for some strange reason I've thought that it will be interesting to test it on Intel platform also, so I've run it on my work machine with Intel Pentium 4 3 Ghz. The results were little strange:





TestStatic: 17031359
TestStaticNoInline: 61250392
TestInterface: 55000352
TestStaticDelegate: 51250328
TestStaticNoInlineDelegate: 49844069
TestInterfaceDelegate: 34218969


Woow, it seems that on Intel, Delegates are something around 7% faster then interface call. What's more, delegate instance call is 40% faster then interface one.

I've tried to find some bug in this code, but everything looks, fine for me. Event tried to change (DateTime.Now - start).Ticks to (DateTime.Now - start).ToString() but results were similiar:





TestStatic: 00:00:01.6875108
TestStaticNoInline: 00:00:06.0625388
TestInterface: 00:00:05.5312854
TestStaticDelegate: 00:00:05.0625324
TestStaticNoInlineDelegate: 00:00:05.2187834
TestInterfaceDelegate: 00:00:03.4687722


Seeing this, I saw also that even on AMD, delegate instance call (TestInterfaceDelegate) is faster then normal interface call (TestInterface).



 



My Conclusions




  • If you need performance use static methods and TypeMock for testing


  • If you need to inject some code at runtime you can freely use Delegates without any significant performance loose. Even better - it looks like .NET 3.5 JIT promotes functional programing (static methods and delegates) by making it more performant than object oriented (interface calls) one - at least on Intel platform.


  • If you don't wan't to use TypeMock you can safely use Delegates based Dependency Injection.

piątek, 9 maja 2008

How to test static method call in c# without TypeMock

If you think - like me - that TDD is not only testing, but also design process, you probably are using Rhino Mocks or any other mocking tool that is based on interface/virtual methods implementation. But how can one mock a call to static method like

File.Open("...", FileMode.Open);

The only method until now (for me) was to use Typemock. But until you buy a paid version, you cannot use it without typing method names as string - which I don't like.

After long long long searching I think I found an alternative - Delegates.

Lets see an example (in c# 3.5):

    public class SomeClass
    {
       
public Func<string, FileMode, Stream> FileOpen = File.Open;

       
public void DoSomethingWithFile(string path)
        {
           
using (Stream s = FileOpen(path, FileMode.Open))
            {
               
//...
                //...
            }
        }
    }

As you see, I placed a public delegate to File.Open which I can change later in my test

        [Test]
       
public void TestDoSomethingWithFile()
        {
           
SomeClass sc = new SomeClass();
           
string testFile = Path.GetTempFileName();
           
//...fill this testFile

            sc.FileOpen = delegate { return File.OpenRead(testFile); };
            sc.DoSomethingWithFile(testFile);
            
           
//Asserts here
        }

What is great about such pattern is that you can treat those delegates in class as references in dll file.
Another great thing is that you can use Dependency Injection to inject static(or not) methods to the class.

 

And thats it. So simple right?
Ummm, but what about performance, delegates can't work as fast as static calls.
Well, I'll show you some benchmarks later.

Welcome

So the time has come for me also :-)

It's time to try to archive some of my thougths about programming and whatever comes to my mind.

So let's start and see what will come from this :-)

 

First of all (and this is the only time I will do it) I want to apologize for my English. I'm not a native English speaker so be ready for mistakes :-)