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 :)