C#/.NET Little Pitfalls

88,847 views

Published on

C# is a great programming language for modern development. Like any language, however, there are parts of the language and BCL that can trip you up if you have invalid assumptions as to what is going on behind the scenes. This presentation discusses a few of these pitfalls and how to avoid them.

Published in: Technology
2 Comments
15 Likes
Statistics
Notes
No Downloads
Views
Total views
88,847
On SlideShare
0
From Embeds
0
Number of Embeds
82,047
Actions
Shares
0
Downloads
220
Comments
2
Likes
15
Embeds 0
No embeds

No notes for slide

C#/.NET Little Pitfalls

  1. 1. James Michael Hare 2011 Visual C# MVP Application Architect Scottrade August 5 th , 2011 Blog: http://www.BlackRabbitCoder.net Twitter: @BlkRabbitCoder
  2. 2. The joys of managed code <ul><li>Modern programming languages are extremely advanced and give us great frameworks that allow us to develop software more easily: </li></ul><ul><ul><li>Higher levels of abstraction </li></ul></ul><ul><ul><li>Managed Memory / Garbage Collection </li></ul></ul><ul><ul><li>Better management of resources </li></ul></ul><ul><ul><li>Stronger type safety </li></ul></ul><ul><ul><li>Processor / Platform independence </li></ul></ul><ul><ul><li>Shorter learning curve </li></ul></ul><ul><ul><li>Faster time to market </li></ul></ul>
  3. 3. But, there is a catch… <ul><li>Managed languages do a lot of work for the developer which may cause issues if invalid assumptions or accidental misuse occur. </li></ul><ul><li>Knowing your programming language well is the best way to guard against pitfalls: </li></ul><ul><ul><li>Know what the language tries to do for you behind the scenes. </li></ul></ul><ul><ul><li>Know the default actions of your language if no other direction given. </li></ul></ul><ul><ul><li>Know what is compile-time and run-time behavior in your language. </li></ul></ul>
  4. 4. Today’s Little Pitfalls: <ul><li>Converting boxed values. </li></ul><ul><li>Creating unnecessary strings. </li></ul><ul><li>Nullable<T> arithmetic and comparison operators. </li></ul><ul><li>Mutable readonly values. </li></ul><ul><li>Compile-time const value substitutions. </li></ul><ul><li>Operator overloads are not overrides. </li></ul><ul><li>Hiding is the default, not overriding. </li></ul><ul><li>Default parameters are compile-time substitutions. </li></ul><ul><li>Differences between struct and class . </li></ul><ul><li>Order of initialization differences in .NET languages. </li></ul><ul><li>Premature polymorphism in construction. </li></ul><ul><li>Unconstrained generic type parameters are compiled like object . </li></ul>
  5. 5. Converting boxed values <ul><li>We all know we can cast a double to an int , right? </li></ul><ul><li>But what if the double were boxed? </li></ul><ul><li>The former works, latter throws InvalidCastException, since no conversion from object -> int it’s a cast . </li></ul><ul><li>Remember when unboxing must use correct type (important when reading from IDataReader , etc) </li></ul>
  6. 6. Creating unnecessary strings <ul><li>If building string over multiple statements, don’t do: </li></ul><ul><li>But do: </li></ul><ul><li>Latter is 100x faster (literally) and creates less garbage. </li></ul>
  7. 7. Creating unnecessary strings <ul><li>When comparing strings insensitively, use insensitive IComparer , or form of Compare () which takes case-insensitive parameter, do not convert string! </li></ul><ul><li>Not: </li></ul><ul><li>But: </li></ul><ul><li>Latter is faster and doesn’t create temp upper-case string that needs to be garbage collected. </li></ul>
  8. 8. Creating unnecessary strings <ul><li>When you need to join two strings, by all means use concatenation, it’s the fastest and most concise. </li></ul><ul><li>But, when you are building a string over multiple statements (like a loop), concatenating creates a lot of intermediate strings that need to be garbage collected. </li></ul><ul><li>This allocation and collection can cause some overhead if done too often, and is much slower. </li></ul><ul><li>Thus, Prefer to use StringBuilder when building a string over multiple statements. </li></ul><ul><li>Prefer to use insensitive variants of Compare() and Equals() instead of ToUpper()/ToLower() compares. </li></ul>
  9. 9. Nullable<T> arithmetic <ul><li>Value types cannot truly be null , always has a value. </li></ul><ul><li>We can “simulate” a nullable value type using the System.Nullable<T> wrapper (can use T? shorthand). </li></ul><ul><li>This wrapper is a struct which has two key members: </li></ul><ul><ul><li>HasValue – property that states whether null or not. </li></ul></ul><ul><ul><li>Value – property that returns the wrapped value if HasValue is true and throws InvalidOperationException if not. </li></ul></ul><ul><li>In addition, we can compare and assign nullable value types to null , though this is not same as null reference. </li></ul>
  10. 10. Nullable<T> arithmetic <ul><li>To make nullable value types more usable, the arithmetic operators supported by the wrapped type are supported on the nullable-wrapped type as well. </li></ul><ul><li>That is you can directly add two int? since int supports operator+ . </li></ul><ul><li>This is true of the primitive and BCL value types as well as your own custom value types. </li></ul><ul><li>The problem happens when you attempt to use one of these operators on a nullable type with “ null ” value. </li></ul>
  11. 11. Nullable<T> arithmetic <ul><li>For example, let’s say you have: </li></ul>
  12. 12. Nullable<T> arithmetic <ul><li>And you perform: </li></ul><ul><li>What is the result? </li></ul><ul><ul><li>Does it compile? </li></ul></ul><ul><ul><li>Does it throw at runtime? </li></ul></ul><ul><ul><li>Does it succeed? </li></ul></ul>
  13. 13. Nullable<T> arithmetic <ul><li>It does indeed “succeed”, but the value will be a “ null ” Fraction? That is , one with HasValue == false . </li></ul><ul><li>Arithmetic operations on nullable types where either operand is “ null ” returns a “ null ” instance. </li></ul><ul><li>You can see a hint of this if you hover over h : </li></ul><ul><li>Since h was a Fraction? we have a pretty good hint result will be “ null ” if either operand is “ null ”. </li></ul>
  14. 14. Nullable<T> arithmetic <ul><li>If we chose a result type of Fraction instead of Fraction? , we’d get a compiler error which would force us to use the Value property to resolve: </li></ul>
  15. 15. Nullable<T> comparisons <ul><li>A similar thing occurs between nullable-wrapped value types and some of the comparison operators. </li></ul><ul><li>The == and != operators work exactly as you’d expect. </li></ul><ul><li>The <=, <, >=, and > operators, however, will all return false if either operand is “ null ”. </li></ul><ul><li>This is true even if both are “ null ” on >= and <= : </li></ul>
  16. 16. Nullable<T> comparisons <ul><li>Note that this means you can not rely on logic ladders like this for Nullable<T > wrapped types: </li></ul>
  17. 17. Nullable<T> comparisons <ul><li>Remember that arithmetic operators on a “ null ” instance of a Nullable<T> wrapped value type yield a “ null ” result. </li></ul><ul><ul><li>To prevent, make result of operation non-nullable and compiler will force you to use Value property. </li></ul></ul><ul><li>Remember that the <=, >=, <, and > operators on a “ null ” instance of a Nullable<T> wrapped value type yield a false result. </li></ul><ul><ul><li>No way to directly prevent this, just have to watch out and avoid or understand the underlying logic. </li></ul></ul>
  18. 18. Mutable readonly values <ul><li>readonly defines a run-time, read-only value: </li></ul><ul><li>This lets you assign a value once to the field in an initializer or constructor, and then it can’t be changed. </li></ul><ul><li>For value types, this works much as you’d expect. </li></ul>
  19. 19. Mutable readonly values <ul><li>This means the readonly field is immutable… right? </li></ul><ul><li>Strictly speaking, yes it does. </li></ul><ul><li>Practically speaking, it’s a bit more complicated. </li></ul><ul><li>Consider the following snippet of code where we attempt to define a readonly List<T> of options: </li></ul>
  20. 20. Mutable readonly values <ul><li>Is the object _availableOptions refers to immutable? </li></ul><ul><ul><li>No, readonly modifies the reference being declared. </li></ul></ul><ul><ul><li>Means once assigned, it can’t be reassigned new object. </li></ul></ul><ul><ul><li>However, this does not mean that the object referred to by _availableOptions cannot be modified. </li></ul></ul>
  21. 21. Mutable readonly values <ul><li>Must remember that readonly refers to the identifier, not the value it labels. </li></ul><ul><ul><li>For value types, this is the actual value and will be immutable. </li></ul></ul><ul><ul><li>For reference types, this is the object reference which will itself be immutable, but the object referred to is not. </li></ul></ul><ul><li>If you want to make a readonly reference type immutable, must be an immutable type: </li></ul><ul><ul><li>String </li></ul></ul><ul><ul><li>ReadOnlyCollection </li></ul></ul>
  22. 22. const values are compile-time <ul><li>readonly is a “constant” resolved at run-time. </li></ul><ul><li>However, const is resolved at compile-time. </li></ul><ul><li>This has a few interesting ramifications: </li></ul><ul><ul><li>const values must be compile-time definitions. </li></ul></ul><ul><ul><li>const values are substituted at compile-time. </li></ul></ul><ul><ul><li>const identifiers are already static by definition. </li></ul></ul>
  23. 23. const values are compile-time <ul><li>Consider this class in a class library Order.DLL : </li></ul><ul><li>Now, we use this in a program, OrderServer.EXE: </li></ul>
  24. 24. const values are compile-time <ul><li>If we run this, we get the results we expect: </li></ul><ul><ul><li>Max parts per order is: 2 </li></ul></ul><ul><ul><li>Max open orders per customer is: 5 </li></ul></ul><ul><li>What if we double both to 4/10 in Orders.dll, build Orders.dll but deploy only the changed assembly? </li></ul><ul><ul><li>Max parts per order is: 2 </li></ul></ul><ul><ul><li>Max open orders per customer is: 10 </li></ul></ul><ul><li>The const was substituted originally at compile of OrderServer.exe, since we only built the changed Orders.dll, OrderServer.exe never saw the change. </li></ul>
  25. 25. const values are compile-time <ul><li>If you have a “constant” that is likely to change, consider making it static readonly instead, in this way it is a run-time value. </li></ul><ul><li>Only use const in cases where you are not likely to have changes across compilation units: </li></ul><ul><ul><li>When the const is very unlikely to change, for example a const for the number of days in a week. </li></ul></ul><ul><ul><li>When the const is private and thus not visible to outside classes or other compilation units. </li></ul></ul>
  26. 26. Operators are overloaded <ul><li>Operators can be overloaded in .NET languages. </li></ul><ul><li>Allows you to use operators to perform actions between objects in a meaningful way. </li></ul><ul><li>For instance, DateTime and operator - : </li></ul><ul><li>Works great when operator meaningful to the type. </li></ul>
  27. 27. Operators are overloaded <ul><li>The pitfall comes when people think operators are overridden, instead of overloaded. </li></ul><ul><li>For example, what if we defined a class for Fraction with a Numerator and Denominator and == overload: </li></ul>
  28. 28. Operators are overloaded <ul><li>What happens if we attempt to use the following snippet of code: </li></ul><ul><li>This does not call Fraction ’s operator == overload because oneHalf and anotherOneHalf are references to object, not Fraction. </li></ul><ul><li>Operator definitions are overloaded for specific types. </li></ul>
  29. 29. Operators are overloaded <ul><li>Remember that operators are overloaded, not overridden. </li></ul><ul><li>The operator implementation used is based on the type of the references, not the objects they refer to. </li></ul><ul><li>Be especially wary of == and != overloads since these are defined for object and will not give you a compiler error if used incorrectly. </li></ul><ul><li>When using generics, remember that a non-narrowed generic type parameter always assumes object (coming up in a later pitfall). </li></ul>
  30. 30. Hiding is the default <ul><li>When you want to “replace” the behavior of a method (or property) from a base class with one from a sub class, you have two basic choices: </li></ul><ul><ul><li>Overriding : </li></ul></ul><ul><ul><ul><li>Base class member must be abstract, virtual, or override. </li></ul></ul></ul><ul><ul><ul><li>Member resolved at run-time based on type of object . </li></ul></ul></ul><ul><ul><ul><li>No direct way to get to original member from outside. </li></ul></ul></ul><ul><ul><li>Hiding: </li></ul></ul><ul><ul><ul><li>Base class member can be anything but abstract . </li></ul></ul></ul><ul><ul><ul><li>Member resolved at compile-time based on type of reference. </li></ul></ul></ul><ul><ul><ul><li>Can directly get to the original member using cast, etc. </li></ul></ul></ul>
  31. 31. Hiding is the default <ul><li>The default behavior is to hide , not override even if base-class member declared virtual or override: </li></ul>
  32. 32. Hiding is the default <ul><li>This means that which method is invoked depends solely on the reference: </li></ul><ul><li>The object referenced in each case is irrelevant, method is non-virtual, thus resolved at compile-time. </li></ul>
  33. 33. Hiding is the default <ul><li>Watch your compiler warnings! Implicit hiding is not an error, but it will give a compiler warning. </li></ul><ul><li>Consider treating warnings as errors in your projects. </li></ul><ul><li>Whenever you do intentionally hide, make the hide explicit by using the new keyword: </li></ul>
  34. 34. Default parameters <ul><li>Can specify a default for a parameter whose argument is not specified in a method or constructor call. </li></ul><ul><li>Can reduce need for multiple overloads. </li></ul><ul><li>Arguments are defaulted left to right if not specified ( or can be arbitrary if using named arguments ) </li></ul>
  35. 35. Default parameters <ul><li>The problem is, default parameter values are substituted at compile-time, not run-time. </li></ul><ul><li>Default value substitutions are based on the reference and not the object itself. </li></ul><ul><li>Default parameter values are not inherited from base classes or interfaces. </li></ul><ul><li>Default parameter values are not required to be consistent between interfaces, classes, sub classes, etc. </li></ul><ul><li>Subject to same compile-time change issues across compilation units as const values. </li></ul>
  36. 36. Default parameters <ul><li>Consider: </li></ul>
  37. 37. Default parameters <ul><li>What happens: </li></ul><ul><li>Because resolved at compile-time, we will get: </li></ul><ul><ul><li>SubTag </li></ul></ul><ul><ul><li>BaseTag </li></ul></ul><ul><ul><li>ITag </li></ul></ul>
  38. 38. Default parameters <ul><li>Avoid specifying default parameters in interfaces. </li></ul><ul><li>Avoid specifying default parameters in non-sealed classes that are likely to be sub-classed. </li></ul><ul><li>Prefer to only use default parameters in sealed methods or classes. </li></ul><ul><li>If parameter values refer to public properties that will be passed in during construction, consider object initialization syntax instead. </li></ul>
  39. 39. A struct is not a class <ul><li>In C++, structures and classes are nearly identical with the exception of default accessibility level. </li></ul><ul><li>This is not true in .NET since they have completely different semantics. </li></ul><ul><li>The most notable difference between the two is that classes are reference types, and structures are value types. </li></ul><ul><li>This means whenever you are passing or return a structure to or from a method, you are returning a copy of the value and not just a reference to the original. </li></ul>
  40. 40. A struct is not a class <ul><li>If you aren’t aware of the value type semantics of a struct , you may write code that won’t function correctly, fails to compile, or is less performant: </li></ul><ul><ul><li>Passing a struct as a return value or parameter is a copy. </li></ul></ul><ul><ul><li>Assigning a struct creates a new copy. </li></ul></ul><ul><ul><li>Subscribing to event in copy has no effect on original. </li></ul></ul><ul><ul><li>Default constructors of a struct cannot be suppressed. </li></ul></ul><ul><ul><li>Cannot change default value of struct members. </li></ul></ul><ul><ul><li>Cannot inherit from a struct (implicitly sealed). </li></ul></ul><ul><ul><li>Defining struct > ~16 bytes can degrade performance. </li></ul></ul>
  41. 41. A struct is not a class <ul><li>Assuming: </li></ul><ul><li>What happens: </li></ul>
  42. 42. A struct is not a class <ul><li>A struct passed or returned by value is a copy, modifying the copy does not affect original. </li></ul><ul><li>Important because struct returned as a property can’t be modified (compiler error) since you’d be directly modifying a temp copy and not original property. </li></ul><ul><li>Also creates issues when attempting to subscribe to an event in a copy when you intended to subscribe to original. </li></ul><ul><li>If struct is too large, these copies can be detrimental to performance, recommended size is < ~16 bytes. </li></ul>
  43. 43. A struct is not a class <ul><li>If you subscribe to a copy you do not subscribe to original. </li></ul>
  44. 44. A struct is not a class <ul><li>This will have no output, because subscribed to copy of event in AddEventHandler() and not original event in Main() : </li></ul>
  45. 45. A struct is not a class <ul><li>Like class , the default constructor of struct is implicit. </li></ul><ul><li>Unlike class , you cannot specify implementation. </li></ul><ul><li>Unlike class , defining other constructors doesn’t suppress default, nor can you make it non-visible. </li></ul>
  46. 46. A struct is not a class <ul><li>Only create a struct when the internal representation is very small (~16 bytes or less). </li></ul><ul><li>Typically, we prefer value types to behave like primitives in that they can be treated as a single value. </li></ul><ul><li>The best uses of struct tend to be in creating small immutable values, due to value type copy semantics. </li></ul><ul><li>Avoid event definitions in struct . </li></ul><ul><li>Do not attempt to hide default constructor for a struct , you can’t. </li></ul>
  47. 47. Initialization Order <ul><li>When you create a class hierarchy and then construct a sub-class, in what order are the objects constructed? </li></ul><ul><li>The answer? It depends on your language… </li></ul><ul><li>Given this little class that simply logs a message on construction, which we’ll use to show initialization: </li></ul><ul><li>Let’s look at an example. </li></ul>
  48. 48. Initialization Order <ul><li>Consider this base class for an abstract Shape: </li></ul><ul><li>Note that we are initializing a field ( LogMe() will write a message to console). </li></ul>
  49. 49. Initialization Order <ul><li>Now consider this sub-class for a concrete Rectangle: </li></ul>
  50. 50. Initialization Order <ul><li>What happens if we construct a Rectangle(3,5) in C#? </li></ul><ul><li>But if we translate the same code to VB.NET? </li></ul>
  51. 51. Initialization Order <ul><li>You should know the differences in initialization order, but also strive not to write code dependent on it: </li></ul><ul><ul><li>C#: </li></ul></ul><ul><ul><ul><li>Sub-class initialization </li></ul></ul></ul><ul><ul><ul><li>Base-class initialization </li></ul></ul></ul><ul><ul><ul><li>Base-class constructor </li></ul></ul></ul><ul><ul><ul><li>Sub-class constructor </li></ul></ul></ul><ul><ul><li>VB.NET: </li></ul></ul><ul><ul><ul><li>Base-class initialization </li></ul></ul></ul><ul><ul><ul><li>Base-class constructor </li></ul></ul></ul><ul><ul><ul><li>Sub-class initialization </li></ul></ul></ul><ul><ul><ul><li>Sub-class constructor </li></ul></ul></ul>
  52. 52. Premature polymorphism <ul><li>Consider again the Shape and Rectangle puzzle from the previous pitfall. </li></ul><ul><li>Let’s alter the Shape class to call a polymorphic ( virtual or abstract ) method from the constructor: </li></ul>
  53. 53. Premature polymorphism <ul><li>Remembering that Rectangle looks like this: </li></ul>
  54. 54. Premature polymorphism <ul><li>So what happens if we construct a new Rectangle(3,5) , remembering that now the Shape default constructor will perform a polymorphic draw? </li></ul><ul><li>This will compile successfully, but what will the result be? </li></ul>
  55. 55. Premature polymorphism <ul><li>You might expect an explosion, because Draw() is abstract in the base, but even though Rectangle hasn’t been constructed yet, object still considers itself a Rectangle . </li></ul><ul><li>This means it will call the Rectangle.Draw() method. </li></ul><ul><li>But there’s a problem, Rectangle’s constructor has not run yet, thus _length and _width are unassigned: </li></ul>
  56. 56. Premature polymorphism <ul><li>Polymorphic calls in base class constructors are problematic since the sub-class constructor has not yet been executed, which may have class in inconsistent state. </li></ul><ul><li>Also remember that due to the order of initialization differences between VB.NET/C#.NET, the sub-class initializers will not have run yet for VB.NET as well. </li></ul><ul><li>Safer to just avoid polymorphic calls in a base class constructor altogether. </li></ul>
  57. 57. Unconstrained generics <ul><li>When you create a generic, you specify a generic type parameter. </li></ul><ul><li>You can constrain the generic type parameter to specify it must be of a certain type. </li></ul><ul><li>Narrowing allows you to treat variables of the generic type to act like the narrowed type, thus exposing their members. </li></ul>
  58. 58. Unconstrained generics <ul><li>Let’s constraint a generic that it must be a reference type ( class ) but not constrain it’s type: </li></ul><ul><li>What does that mean if we do this? </li></ul><ul><li>What’s the output given T is type string ? </li></ul>
  59. 59. Unconstrained generics <ul><li>The result was false ! </li></ul><ul><li>This is because the == comparison between the unconstrained type T assumes object. </li></ul><ul><li>Thus even though T is type string in our usage, when the generic is compiled, it is compiled assuming object . </li></ul><ul><li>This means that object’s == is invoked and not string ’s. </li></ul><ul><li>Polymorphic overrides work as you’d expect, but for hides and operator overloads, the resolution depends on the type constraint ( object if unconstrained). </li></ul>
  60. 60. Unconstrained generics <ul><li>Remember that a generic type parameter that is not constrained to a specific base-type or interface is considered to be constrained to object implicitly. </li></ul><ul><li>Whatever the generic type parameter is constrained to, any operator overloads or method hides will resolve to that constrained type if available and not the actual generic type argument used in the call. </li></ul>
  61. 61. Conclusion <ul><li>.NET is an excellent development platform offering: </li></ul><ul><ul><li>Great execution performance. </li></ul></ul><ul><ul><li>Ease of development. </li></ul></ul><ul><ul><li>Quick time to market. </li></ul></ul><ul><ul><li>Fewer bugs and memory leaks. </li></ul></ul><ul><li>There are a few interesting features of the language that if not understood can lead to pitfalls. </li></ul><ul><li>Knowing those pitfalls, why they occur, and how to avoid them are key strengths for .NET developers. </li></ul>
  62. 62. Questions?

×