Obfuscation is the usual approach and even the free version with no preparation can create grave difficulties for a hacker. But it can also break your app- for example, if you use reflection. This is why IMHO obfuscation is best planned from the start, preferably using "declarative" obfuscation and strategies like overloading of identifiers to make decompiled code practically unusable.
Another common trick is to mess with your app so it no longer looks like an assembly, meaning it can't be decompiled. Example: .NET reactor. But every time this sort of protection appears, hackers find a way to unpack it so your work can be viewed in reflector.
Assume that anything you distribute can be decompiled. IMHO the best strategy is to make use of the decompiled code so onerous that it simply isn't worth it.
If that's still not enough then you need to prevent physical access to the app, generally by hosting it yourself using an ASP or similar model.
"... They ne'er cared for us
yet: suffer us to famish, and their store-houses
crammed with grain; make edicts for usury, to
support usurers; repeal daily any wholesome act
established against the rich, and provide more
piercing statutes daily, to chain up and restrain
the poor. If the wars eat us not up, they will; and
there's all the love they bear us."
-- Shakespeare: Coriolanus, Act 1, scene 1