Telerik JustDecompile second impressions

by toni 22. February 2012 17:56

Few days ago I wrote about Telerik’s JustDecompile. I was using debug build of assembly and comparing the decompiled result to the original code and to the output produced by ILSpy.

For my surprise there was a significant difference between the output produced by JustDecompile and ILSpy. I got a comment from Telerik on my blog that explained this (you can read the full comment here):

The devil as always is in the details. It's been JustDecompile's philosophy to stay as close to the MSIL in the input assemblies as possible. When you compile something in Debug the MSIL produced by the compiler is identical to the C# produced by JustDecompile in the examples above. If you compile these assemblies in Release then the MSIL will closely resemble original code/ILSpy output. In that case JustDecompile will produce code strongly resembling ILSpy output.

That comment in mind I took the release version of AppSettings and compared the results.

Original code

public static object Convert(Type type, 
    string value, IFormatProvider formatProvider)
{
    // In case the type is e.g. Nullable<int>
    if (type.IsGenericType && type.GetGenericTypeDefinition() == 
        typeof(Nullable<>))
    {
        if (string.Empty == value)
        {
            return (object)null;
        }

        // Type is nullable and we have value is not empty.
        // Get the underlying type and continue the conversion.
        type = Nullable.GetUnderlyingType(type);
    }

    if (type.IsEnum)
    {
        return ConvertEnum(type, value);
    }

    return System.Convert.ChangeType(value, type, formatProvider);
}

private static object ConvertEnum(Type type, string value)
{
    return Enum.Parse(type, value, true);
}

JustDecompile

public static object Convert(Type type, 
    string value, IFormatProvider formatProvider)
{
    if (type.IsGenericType && type.GetGenericTypeDefinition() == 
        typeof(Nullable<>))
    {
        if (string.Empty != value)
        {
            type = Nullable.GetUnderlyingType(type);
        }
        else
        {
            return null;
        }
    }
    if (!type.IsEnum)
    {
        return Convert.ChangeType(value, type, formatProvider);
    }
    else
    {
        return TypeConverter.ConvertEnum(type, value);
    }
}

ILSpy

public static object Convert(Type type, 
    string value, IFormatProvider formatProvider)
{
    if (type.IsGenericType && type.GetGenericTypeDefinition() == 
        typeof(Nullable<>))
    {
        if (string.Empty == value)
        {
            return null;
        }
        type = Nullable.GetUnderlyingType(type);
    }
    if (type.IsEnum)
    {
        return TypeConverter.ConvertEnum(type, value);
    }
    return Convert.ChangeType(value, type, formatProvider);
}

As you can see the output makes more sense. There are no additional flag variables or if else branches. Now I’m wondering what ILSpy does when it shows me the debug version of the assembly. Maybe it is doing some kind of optimization when it decompiles the code? I spent some time trying different options but the end result was always the same.

To sum it all up I like the way Telerik and team behind JustDecompile does things:

It's been JustDecompile's philosophy to stay as close to the MSIL in the input assemblies as possible.

Even though I hope I don’t have to decompile code often it is good to have good tool to do it.

Telerik JustDecompile first impressions

by toni 18. February 2012 22:23

Just downloaded Telerik’s JustDecompile. The installation requires you to give your email address so that was a bit disappointing but let’s see what kind of results it can produce. For these tests I used debug build (AnyCPU) of my AppSettings library (Yes, I need to give some love to that library).

Update 22.2.2012 I compared the results using release build.

 

Original code

public static string GetPropertyValue(
    object instance, 
    PropertyInfo propertyInfo, 
    IFormatProvider formatProvider)
{
    var tempValue = propertyInfo.GetValue(instance, null);
    if (null == tempValue)
    {
        return string.Empty;
    }

    var value = Convert.ToString(tempValue, formatProvider);
    return value;
}

JustDecompile

public static string GetPropertyValue(
    object instance, 
    PropertyInfo propertyInfo, 
    IFormatProvider formatProvider)
{
    string empty;
    object tempValue = propertyInfo.GetValue(instance, null);
    bool flag = null != tempValue;
    if (flag)
    {
        string @value = Convert.ToString(tempValue, formatProvider);
        empty = @value;
    }
    else
    {
        empty = string.Empty;
    }
    return empty;
}

As you can see the decompiled version is more complex. Sometimes that is normal but when you compare JustDecompile with ILSpy you get very different results.

Original code

public static bool IsConnectionString(PropertyInfo propertyInfo)
{
    var attribute = propertyInfo
        .GetCustomAttribute<SettingPropertyAttribute>();
    return null != attribute && attribute.IsConnectionString;
}

JustDecompile

public static bool IsConnectionString(PropertyInfo propertyInfo)
{
    bool isConnectionString;
    SettingPropertyAttribute attribute = propertyInfo
        .GetCustomAttribute<SettingPropertyAttribute>();
    if (attribute == null)
    {
        isConnectionString = false;
    }
    else
    {
        isConnectionString = attribute.IsConnectionString;
    }
    bool flag = isConnectionString;
    return flag;
}

ILSpy

public static bool IsConnectionString(PropertyInfo propertyInfo)
{
    SettingPropertyAttribute attribute = propertyInfo
        .GetCustomAttribute<SettingPropertyAttribute>();
    return attribute != null && attribute.IsConnectionString;
}

How about another example of code that can be used to convert type to another type.

Original code

public static object Convert(
    Type type, 
    string value, 
    IFormatProvider formatProvider)
{
    // In case the type is e.g. Nullable<int>
    if (type.IsGenericType && 
        type.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        if (string.Empty == value)
        {
            return (object)null;
        }

        // Type is nullable and we have value is not empty.
        // Get the underlying type and continue the conversion.
        type = Nullable.GetUnderlyingType(type);
    }

    if (type.IsEnum)
    {
        return ConvertEnum(type, value);
    }

    return System.Convert.ChangeType(value, type, formatProvider);
}

JustDecompile

public static object Convert(
    Type type, 
    string value, 
    IFormatProvider formatProvider)
{
    object obj;
    bool genericTypeDefinition;
    if (!type.IsGenericType)
    {
        genericTypeDefinition = true;
    }
    else
    {
        genericTypeDefinition = !(type.GetGenericTypeDefinition() 
            == typeof(Nullable`1));
    }
    bool empty = genericTypeDefinition;
    if (!empty)
    {
        empty = !(string.Empty == value);
        if (empty)
        {
            type = Nullable.GetUnderlyingType(type);
            empty = !type.IsEnum;
            if (empty)
            {
                obj = Convert.ChangeType(value, 
                    type, formatProvider);
            }
            else
            {
                obj = TypeConverter.ConvertEnum(type, value);
            }
            return obj;
        }
        obj = null;
        return obj;
    }
    empty = !type.IsEnum;
    if (empty)
    {
        obj = Convert.ChangeType(value, type, formatProvider);
    }
    else
    {
        obj = TypeConverter.ConvertEnum(type, value);
    }
    return obj;
}

ILSpy

public static object Convert(
    Type type, 
    string value, 
    IFormatProvider formatProvider)
{
    object result;
    if (type.IsGenericType && 
        type.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        if (string.Empty == value)
        {
            result = null;
            return result;
        }
        type = Nullable.GetUnderlyingType(type);
    }
    if (type.IsEnum)
    {
        result = TypeConverter.ConvertEnum(type, value);
    }
    else
    {
        result = Convert.ChangeType(value, type, formatProvider);
    }
    return result;
}

Next static method that is used to create new object

Original code

public static AppSettings CreateForAssembly(
    Assembly assembly, FileOption fileOption)
{
    if (null == assembly)
    {
        throw new ArgumentNullException("assembly", 
            "Cannot create AppSettings. Assembly is null.");
    }

    var fullPath = GetPathToConfigurationFile(assembly);

    var appSettings = new AppSettings
    {
        Configuration = OpenConfigurationFile(fullPath, fileOption)
    };

    return appSettings;
}

JustDecompile

public static AppSettings CreateForAssembly(
    Assembly assembly, FileOption fileOption)
{
    bool flag = !(null == assembly);
    if (flag)
    {
        string fullPath = AppSettings.GetPathToConfigurationFile(
             assembly);
        AppSettings appSetting = new AppSettings();
        appSetting.Configuration = 
           AppSettings.OpenConfigurationFile(fullPath, fileOption);
        AppSettings appSettings = appSetting;
        AppSettings appSetting1 = appSettings;
        return appSetting1;
    }
    else
    {
        throw new ArgumentNullException("assembly",
           "Cannot create AppSettings. Assembly is null.");
    }
}

ILSpy

public static AppSettings CreateForAssembly(
    Assembly assembly, FileOption fileOption)
{
    if (null == assembly)
    {
        throw new ArgumentNullException("assembly", 
        "Cannot create AppSettings. Assembly is null.");
    }
    string fullPath = AppSettings
        .GetPathToConfigurationFile(assembly);
    return new AppSettings
    {
        Configuration = AppSettings.OpenConfigurationFile(
           fullPath, fileOption)
    };
}

As you can see ILSpy produces result that is closer to the original code. That doesn’t mean JustDecompile is a bad product. It only means there is room for improvement. I’ve had really good experience using other Telerik’s products and their support has been excellent so I’m sure JustDecompile will grow to be a very good tool. Finally I hope I win the t-shirt :)

My most used extension method

by toni 12. February 2012 18:50

There are lot of libraries that have tens or hundreds of extension methods. Just look into PGK Extensions, dotNetExt or Extension Method Extravaganza. The extensionmethod.net is also a good site where you can search or browse for extension methods.

I usually add new extension methods into the project as I need them but there is one that I use in every project.

public static string FormatWith(
    this string value, params object[] args)
{
    return string.Format(CultureInfo.InvariantCulture, 
        value, args);
}

This method is especially useful when writing anything into the log file because it uses the invariant culture. Some might also argue that it makes the code easier to read.

throw new Exception(string.Format("The value {0} is invalid", value));

throw new Exception(string.Format(CultureInfo.InvariantCulture,
    "The value {0} is invalid", value));

throw new Exception("The value {0} is invalid".FormatWith(value));

If you are using ReSharper you need to use an attribute so that ReSharper can validate you have correct number of parameters when using this extension method. With string.Format and StringBuilder.AppendFormat this comes out of the box. Personally I haven’t used the attribute in any of the projects (requires you to reference JetBrains assembly).

Tags:

Code

How much does it cost to create NHibernate session Part 2

by toni 12. February 2012 18:04

Few days ago I did a short experiment in order to find out how much does it cost to create the session when using NHibernate. I found out that the very first OpenSession call is the most expensive. Calls after that don’t cost anything in normal scenarios.

Performance benchmark data

I downloaded the NHibernate source code (v3.2GA) and run two tests. In the first test I created only single session. Below you can see the elapsed time (ms), number of calls and average time spent on that function.

nhibernate_isession_creation_1

Next I ran the same test but this time I created ten sessions.

nhibernate_isession_creation_10

As you can see from the picture the average time spent on each function is a lot smaller and total amount increased only ~6ms even though we created ten sessions instead of just one.

I didn’t investigate what are the exact reasons for this behavior but looking at the source code there are some static data and constructors (e.g. clrTypeToNhibernateCore) that might explain this. Also there might be some caching involved on .NET Framework when using CreateInstance or CreateDelegate.

Conclusion

My conclusion is that in normal applications (web, desktop etc.) the session creation doesn’t cost anything. By anything I mean it is so cheap operation that you don’t have to worry about it. This is good especially if you are using Dependency Injection container to create and inject the ISession into e.g. ASP.NET MVC3 controller.

How much does it cost to create NHibernate ISession

by toni 9. February 2012 19:25

Creating NHibernate’s ISession is cheap. At least that’s what everyone says. I wanted to know exactly how cheap it is. For this test I’m using NHibernate v3.2. All times are milliseconds unless otherwise noted. Tests were run on a desktop machine (Core i7). All tests were run in isolation meaning I started the test again by running the compiled console application and giving it number of repeats as a command line parameter.

Update 12.2.2011 Second part is available.

Results

 

Repeats Create (ms) Create and dispose (ms)
1 19 32
100 23 36
125 24 38
250 30 43
500 39 55
1000 68 82
2000 120 127
4000 222 216
8000 417 402

These results look funny. There is no way creating 100 sessions takes almost as much time as creating single session. It makes no sense. Time to investigate what’s going on.

Creating ISession

Code used to measure how long session creation took

var stopWatch = Stopwatch.StartNew();
ISession tempSession;
for (int i = 0; i < max; i++)
{
    tempSession = SessionFactory.OpenSession();
}
stopWatch.Stop();

Creating and disposing ISession

Code used to measure how long session creation and dispose took.

var stopWatch = Stopwatch.StartNew();
ISession tempSession;
for (int i = 0; i < max; i++)
{
    tempSession = SessionFactory.OpenSession();
    tempSession.Dispose();
}
stopWatch.Stop();

Cost of creating the very first session

As I looked the results I was wondering why creating 100 sessions doesn’t take even twice as long as creating single session. It looks like the first session creation is the expensive one. So I ran the following test.

for (int j = 0; j < 10; j++)
{
    var stopWatch = Stopwatch.StartNew();
    ISession tempSession;
    for (int i = 0; i < max; i++)
    {
        tempSession = SessionFactory.OpenSession();
        tempSession.Dispose();
    }
    stopWatch.Stop();

    Console.WriteLine("{0} sessions created in {1} ms",
        max, stopWatch.ElapsedMilliseconds);                
}

The output in my console window looked like this

10 sessions created in 33 ms
10 sessions created in 0 ms
10 sessions created in 0 ms
10 sessions created in 0 ms
10 sessions created in 0 ms
10 sessions created in 0 ms
10 sessions created in 0 ms
10 sessions created in 0 ms
10 sessions created in 0 ms
10 sessions created in 0 ms

Now it makes sense. Creating the very first session takes time. All the others are really fast. As long as you are not recreating the ISessionFactory (which you shouldn’t do anyway) you only pay the price in the very first session creation.