sobota, 3 października 2009

Syntax sugar, aop and things that mess up with you behind the scenes

The more I play with reflector, the more my head aches ;)
Suppose we have a simple class like this one:
class TestClass
{
public void SomeMethod()
{
Console.WriteLine("doing something");
}

public void SomeMethodWithArgs(int arg)
{
Console.WriteLine("doing something with arg {0}", arg);
}

public string SomeMethodWithArgsAndReturn(int arg, string str)
{
Console.WriteLine("again doing something");
return String.Format("result ({0} + {1} = <foobar>", arg, str);
}

public IEnumerable<int> SomeIteratorMethod()
{
int counter = 0;
while (counter++ < 3)
{
yield return 2 * counter + 3;
}
}
}
When I look at SomeMethodWithArgsAndReturn, in reflector it looks like this:
public string SomeMethodWithArgsAndReturn(int arg, string str)
{
Console.WriteLine("again doing something");
return string.Format("result ({0} + {1} = <foobar>", arg, str);
}
Cool, huh?
First three methods look the same in reflector and in code. But what about SomeIteratorMethod? It has that fancy yield statement. Well, it looks like this:
public IEnumerable<int> SomeIteratorMethod()
{
<someiteratormethod>d__0 V_0 = new <someiteratormethod>d__0(-2);
V_0.<>4__this = this;
return V_0;
}
I also have a new class:
[CompilerGenerated]
private sealed class <someiteratormethod>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{
// Fields
private int <>1__state;
private int <>2__current;
public TestClass <>4__this;
private int <>l__initialThreadId;
public int <counter>5__1;

// Methods
[DebuggerHidden]
public <someiteratormethod>d__0(int <>1__state);
private bool MoveNext();
[DebuggerHidden]
IEnumerator<int> IEnumerable<int>.GetEnumerator();
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator();
[DebuggerHidden]
void IEnumerator.Reset();
void IDisposable.Dispose();

// Properties
int IEnumerator<int>.Current { [DebuggerHidden] get; }
object IEnumerator.Current { [DebuggerHidden] get; }
}
I've hidden implementation but that's just an implementation of an iterator. However, that's a lot of code compared to my simple yield statement.
Some time ago my friend said that he doesn't like most of the new c# 3.0 features (ok, yield was added in c# 2.0 but that's not the point) because that's just syntactic sugar and he wants to know what happens behind the scenes. My opinion was that I don't maintain msil/byte code so I don't care what happens behind the scenes.
BUT maybe i DO care? This post by Eric Lippert clearly shows that I care or at least I should ;)
Ok, that's an example of a compiler doing something behind the scenes, but what if we want to mess up with the code ourselves? Concept of aspect oriented programming and aspect weaver is this kind of messing up with code.
Let's look at PostSharp and create a basic example of logging of entry and exit to the method body. I've created a simple trace attribute using PostSharp:
[Serializable]
public class TraceAttribute : OnMethodBoundaryAspect
{
private void LogMethod(string beginning, MethodExecutionEventArgs eventArgs)
{
StringBuilder result = new StringBuilder();
result.Append("== ");
result.Append(beginning);
result.AppendFormat(" {0}.{1}(", eventArgs.Method.DeclaringType.Name, eventArgs.Method.Name);
//imma charging mah oneliner ;)
result.Append(String.Join(",", (from arg in eventArgs.GetReadOnlyArgumentArray() ?? new object[0] select String.Format("[{0}]", Convert.ToString(arg))).ToArray()));
result.AppendFormat(") -> [{0}]", eventArgs.ReturnValue);
result.Append(" ==");
Console.WriteLine(result.ToString());
}

public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
LogMethod("Entering", eventArgs);
}

public override void OnExit(MethodExecutionEventArgs eventArgs)
{
LogMethod("Leaving", eventArgs);
}
}
I've added TraceAttribute to SomeMethodWithArgsAndReturn:
[TraceAttribute]
public string SomeMethodWithArgsAndReturn(int arg, string str)
{
Console.WriteLine("again doing something");
return String.Format("result ({0} + {1} = <foobar>", arg, str);
}
In the end when I run my app with:
(new TestClass()).SomeMethodWithArgsAndReturn(11, "twelve");
I get nice output:
== Entering TestClass.SomeMethodWithArgsAndReturn([11],[twelve]) -> [] ==
== Leaving TestClass.SomeMethodWithArgsAndReturn([11],[twelve]) -> [result (11 + twelve = <foobar>] ==
But now my simple method in reflector looks completely different:
public string SomeMethodWithArgsAndReturn(int arg, string str)
{
string ~returnValue~1;
MethodExecutionEventArgs ~laosEventArgs~4;
try
{
object[] ~arguments~3 = new object[] { arg, str };
~laosEventArgs~4 = new MethodExecutionEventArgs(<>AspectsImplementationDetails_1.~targetMethod~1, this, ~arguments~3);
<>AspectsImplementationDetails_1.PostSharpTest.TraceAttribute~1.OnEntry(~laosEventArgs~4);
if (~laosEventArgs~4.FlowBehavior == FlowBehavior.Return)
{
return (string) ~laosEventArgs~4.ReturnValue;
}
Console.WriteLine("again doing something");
string CS$1$0000 = string.Format("result ({0} + {1} = <foobar>", arg, str);
~returnValue~1 = CS$1$0000;
~laosEventArgs~4.ReturnValue = ~returnValue~1;
<>AspectsImplementationDetails_1.PostSharpTest.TraceAttribute~1.OnSuccess(~laosEventArgs~4);
~returnValue~1 = (string) ~laosEventArgs~4.ReturnValue;
}
catch (Exception ~exception~2)
{
~laosEventArgs~4.Exception = ~exception~2;
<>AspectsImplementationDetails_1.PostSharpTest.TraceAttribute~1.OnException(~laosEventArgs~4);
switch (~laosEventArgs~4.FlowBehavior)
{
case FlowBehavior.Continue:
return ~returnValue~1;

case FlowBehavior.Return:
return (string) ~laosEventArgs~4.ReturnValue;
}
throw;
}
finally
{
~laosEventArgs~4.ReturnValue = ~returnValue~1;
<>AspectsImplementationDetails_1.PostSharpTest.TraceAttribute~1.OnExit(~laosEventArgs~4);
~returnValue~1 = (string) ~laosEventArgs~4.ReturnValue;
}
return ~returnValue~1;
}
Wow!
Once again, I've got much more code but it's not in my source code. And once again do I maintain that code? Do I have to be aware of it? How much do I have to know about it?
So the answer can be: don't use any syntactic sugar, don't use aop. You're fine with your strings, ints and simple stuff. You understand how they work and you're cool with it. Are you? Did you know e.g. about this: String interning and String.Empty?
Eric Lippert's blog Fabulous Adventures In Coding is full of these stories. And just wait for c# 4.0 - Evil code of the day.

Time for final remarks or maybe just my opinion on this topic.
I really like syntactic sugar and I'm keen on learning aop concepts. The things I wrote about in this post are scary to me but I think we just have to deal with the fact that computer languages are complicated (even if they try to hide it) and concepts that are simple in everyday work hide their complexity behind the scenes. Look at c#. You've got transformation between c# and msil, you've got jit, clr executing all that stuff and somewhere deep, deep below the code you've written there are pure assembly instructions. Syntax sugar doesn't introduce that much complexity compared to all the other stuff that happens implicitly and we don't (have to ;)) know about.
Remember, there's no such thing as free lunch ;)

Brak komentarzy:

Prześlij komentarz