63
James Michael Hare 2011 Visual C# MVP Application Architect Scottrade August 5 th , 2011 Blog: http://www.BlackRabbitCoder.net Twitter: @BlkRabbitCoder

C#/.NET Little Pitfalls

Embed Size (px)

DESCRIPTION

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.

Citation preview

Page 1: C#/.NET Little Pitfalls

James Michael Hare2011 Visual C# MVPApplication Architect

ScottradeAugust 5th, 2011

Blog: http://www.BlackRabbitCoder.netTwitter: @BlkRabbitCoder

Page 2: C#/.NET Little Pitfalls

The joys of managed codeModern programming languages are extremely

advanced and give us great frameworks that allow us to develop software more easily:Higher levels of abstractionManaged Memory / Garbage CollectionBetter management of resourcesStronger type safetyProcessor / Platform independenceShorter learning curveFaster time to market

Page 3: C#/.NET Little Pitfalls

But, there is a catch…Managed languages do a lot of work for the

developer which may cause issues if invalid assumptions or accidental misuse occur.

Knowing your programming language well is the best way to guard against pitfalls:Know what the language tries to do for you

behind the scenes.Know the default actions of your language if no

other direction given.Know what is compile-time and run-time

behavior in your language.

Page 4: C#/.NET Little Pitfalls

Today’s Little Pitfalls:Converting boxed values.Creating unnecessary strings.Nullable<T> arithmetic and comparison operators.Mutable readonly values.Compile-time const value substitutions.Operator overloads are not overrides.Hiding is the default, not overriding.Default parameters are compile-time substitutions.Differences between struct and class.Order of initialization differences in .NET languages.Premature polymorphism in construction.Unconstrained generic type parameters are compiled like

object.

Page 5: C#/.NET Little Pitfalls

Converting boxed valuesWe all know we can cast a double to an int, right?

But what if the double were boxed?

The former works, latter throws InvalidCastException, since no conversion from object -> int it’s a cast.

Remember when unboxing must use correct type (important when reading from IDataReader, etc)

Page 6: C#/.NET Little Pitfalls

Creating unnecessary stringsIf building string over multiple statements, don’t

do:

But do:

Latter is 100x faster (literally) and creates less garbage.

Page 7: C#/.NET Little Pitfalls

Creating unnecessary stringsWhen comparing strings insensitively, use

insensitive IComparer, or form of Compare() which takes case-insensitive parameter, do not convert string!

Not:

But:

Latter is faster and doesn’t create temp upper-case string that needs to be garbage collected.

Page 8: C#/.NET Little Pitfalls

Creating unnecessary stringsWhen you need to join two strings, by all means use

concatenation, it’s the fastest and most concise.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.

This allocation and collection can cause some overhead if done too often, and is much slower.

Thus, Prefer to use StringBuilder when building a string over multiple statements.

Prefer to use insensitive variants of Compare() and Equals() instead of ToUpper()/ToLower() compares.

Page 9: C#/.NET Little Pitfalls

Nullable<T> arithmeticValue types cannot truly be null, always has a value.We can “simulate” a nullable value type using the

System.Nullable<T> wrapper (can use T? shorthand).This wrapper is a struct which has two key members:

HasValue – property that states whether null or not.Value – property that returns the wrapped value if

HasValue is true and throws InvalidOperationException if not.

In addition, we can compare and assign nullable value types to null, though this is not same as null reference.

Page 10: C#/.NET Little Pitfalls

Nullable<T> arithmeticTo make nullable value types more usable, the

arithmetic operators supported by the wrapped type are supported on the nullable-wrapped type as well.

That is you can directly add two int? since int supports operator+.

This is true of the primitive and BCL value types as well as your own custom value types.

The problem happens when you attempt to use one of these operators on a nullable type with “null” value.

Page 11: C#/.NET Little Pitfalls

Nullable<T> arithmeticFor example, let’s say you have:

Page 12: C#/.NET Little Pitfalls

Nullable<T> arithmeticAnd you perform:

What is the result?Does it compile?Does it throw at runtime?Does it succeed?

Page 13: C#/.NET Little Pitfalls

Nullable<T> arithmeticIt does indeed “succeed”, but the value will be a

“null” Fraction? That is, one with HasValue == false.

Arithmetic operations on nullable types where either operand is “null” returns a “null” instance.

You can see a hint of this if you hover over h:

Since h was a Fraction? we have a pretty good hint result will be “null” if either operand is “null”.

Page 14: C#/.NET Little Pitfalls

Nullable<T> arithmeticIf 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:

Page 15: C#/.NET Little Pitfalls

Nullable<T> comparisonsA similar thing occurs between nullable-

wrapped value types and some of the comparison operators.

The == and != operators work exactly as you’d expect.

The <=, <, >=, and > operators, however, will all return false if either operand is “null”.

This is true even if both are “null” on >= and <= :

Page 16: C#/.NET Little Pitfalls

Nullable<T> comparisonsNote that this means you can not rely on

logic ladders like this for Nullable<T> wrapped types:

Page 17: C#/.NET Little Pitfalls

Nullable<T> comparisonsRemember that arithmetic operators on a “null”

instance of a Nullable<T> wrapped value type yield a “null” result.To prevent, make result of operation non-nullable

and compiler will force you to use Value property.Remember that the <=, >=, <, and > operators

on a “null” instance of a Nullable<T> wrapped value type yield a false result.No way to directly prevent this, just have to watch

out and avoid or understand the underlying logic.

Page 18: C#/.NET Little Pitfalls

Mutable readonly valuesreadonly defines a run-time, read-only value:

This lets you assign a value once to the field in an initializer or constructor, and then it can’t be changed.

For value types, this works much as you’d expect.

Page 19: C#/.NET Little Pitfalls

Mutable readonly valuesThis means the readonly field is immutable…

right?Strictly speaking, yes it does. Practically speaking, it’s a bit more

complicated.Consider the following snippet of code where

we attempt to define a readonly List<T> of options:

Page 20: C#/.NET Little Pitfalls

Mutable readonly valuesIs the object _availableOptions refers to

immutable? No, readonly modifies the reference being

declared.Means once assigned, it can’t be reassigned

new object.However, this does not mean that the object

referred to by _availableOptions cannot be modified.

Page 21: C#/.NET Little Pitfalls

Mutable readonly valuesMust remember that readonly refers to the

identifier, not the value it labels.For value types, this is the actual value and will

be immutable.For reference types, this is the object reference

which will itself be immutable, but the object referred to is not.

If you want to make a readonly reference type immutable, must be an immutable type:StringReadOnlyCollection

Page 22: C#/.NET Little Pitfalls

const values are compile-timereadonly is a “constant” resolved at run-time.However, const is resolved at compile-time.This has a few interesting ramifications:

const values must be compile-time definitions.

const values are substituted at compile-time.const identifiers are already static by definition.

Page 23: C#/.NET Little Pitfalls

const values are compile-timeConsider this class in a class library

Order.DLL:

Now, we use this in a program, OrderServer.EXE:

Page 24: C#/.NET Little Pitfalls

const values are compile-timeIf we run this, we get the results we expect:

Max parts per order is: 2Max open orders per customer is: 5

What if we double both to 4/10 in Orders.dll, build Orders.dll but deploy only the changed assembly?Max parts per order is: 2Max open orders per customer is: 10

The const was substituted originally at compile of OrderServer.exe, since we only built the changed Orders.dll, OrderServer.exe never saw the change.

Page 25: C#/.NET Little Pitfalls

const values are compile-timeIf you have a “constant” that is likely to

change, consider making it static readonly instead, in this way it is a run-time value.

Only use const in cases where you are not likely to have changes across compilation units:When the const is very unlikely to change, for

example a const for the number of days in a week.

When the const is private and thus not visible to outside classes or other compilation units.

Page 26: C#/.NET Little Pitfalls

Operators are overloadedOperators can be overloaded in .NET languages.Allows you to use operators to perform actions

between objects in a meaningful way.For instance, DateTime and operator - :

Works great when operator meaningful to the type.

Page 27: C#/.NET Little Pitfalls

Operators are overloadedThe pitfall comes when people think

operators are overridden, instead of overloaded.

For example, what if we defined a class for Fraction with a Numerator and Denominator and == overload:

Page 28: C#/.NET Little Pitfalls

Operators are overloadedWhat happens if we attempt to use the following

snippet of code:

This does not call Fraction’s operator == overload because oneHalf and anotherOneHalf are references to object, not Fraction.

Operator definitions are overloaded for specific types.

Page 29: C#/.NET Little Pitfalls

Operators are overloadedRemember that operators are overloaded, not

overridden.The operator implementation used is based on

the type of the references, not the objects they refer to.

Be especially wary of == and != overloads since these are defined for object and will not give you a compiler error if used incorrectly.

When using generics, remember that a non-narrowed generic type parameter always assumes object (coming up in a later pitfall).

Page 30: C#/.NET Little Pitfalls

Hiding is the defaultWhen 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:Overriding:

Base class member must be abstract, virtual, or override. Member resolved at run-time based on type of object. No direct way to get to original member from outside.

Hiding: Base class member can be anything but abstract. Member resolved at compile-time based on type of

reference. Can directly get to the original member using cast, etc.

Page 31: C#/.NET Little Pitfalls

Hiding is the defaultThe default behavior is to hide, not override

even if base-class member declared virtual or override:

Page 32: C#/.NET Little Pitfalls

Hiding is the defaultThis means that which method is invoked depends

solely on the reference:

The object referenced in each case is irrelevant, method is non-virtual, thus resolved at compile-time.

Page 33: C#/.NET Little Pitfalls

Hiding is the defaultWatch your compiler warnings! Implicit

hiding is not an error, but it will give a compiler warning.

Consider treating warnings as errors in your projects.

Whenever you do intentionally hide, make the hide explicit by using the new keyword:

Page 34: C#/.NET Little Pitfalls

Default parametersCan specify a default for a parameter whose

argument is not specified in a method or constructor call.

Can reduce need for multiple overloads.Arguments are defaulted left to right if not

specified (or can be arbitrary if using named arguments)

Page 35: C#/.NET Little Pitfalls

Default parametersThe problem is, default parameter values are

substituted at compile-time, not run-time.Default value substitutions are based on the

reference and not the object itself.Default parameter values are not inherited from

base classes or interfaces.Default parameter values are not required to be

consistent between interfaces, classes, sub classes, etc.

Subject to same compile-time change issues across compilation units as const values.

Page 36: C#/.NET Little Pitfalls

Default parametersConsider:

Page 37: C#/.NET Little Pitfalls

Default parametersWhat happens:

Because resolved at compile-time, we will get:SubTagBaseTagITag

Page 38: C#/.NET Little Pitfalls

Default parametersAvoid specifying default parameters in

interfaces.Avoid specifying default parameters in non-

sealed classes that are likely to be sub-classed.

Prefer to only use default parameters in sealed methods or classes.

If parameter values refer to public properties that will be passed in during construction, consider object initialization syntax instead.

Page 39: C#/.NET Little Pitfalls

A struct is not a classIn C++, structures and classes are nearly identical

with the exception of default accessibility level.This is not true in .NET since they have completely

different semantics.The most notable difference between the two is

that classes are reference types, and structures are value types.

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.

Page 40: C#/.NET Little Pitfalls

A struct is not a classIf 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:Passing a struct as a return value or parameter is a

copy.Assigning a struct creates a new copy.Subscribing to event in copy has no effect on original.Default constructors of a struct cannot be suppressed.Cannot change default value of struct members.Cannot inherit from a struct (implicitly sealed).Defining struct > ~16 bytes can degrade performance.

Page 41: C#/.NET Little Pitfalls

A struct is not a classAssuming:

What happens:

Page 42: C#/.NET Little Pitfalls

A struct is not a classA struct passed or returned by value is a copy,

modifying the copy does not affect original.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.

Also creates issues when attempting to subscribe to an event in a copy when you intended to subscribe to original.

If struct is too large, these copies can be detrimental to performance, recommended size is < ~16 bytes.

Page 43: C#/.NET Little Pitfalls

A struct is not a classIf you subscribe to a copy you do not

subscribe to original.

Page 44: C#/.NET Little Pitfalls

A struct is not a classThis will have no output, because subscribed

to copy of event in AddEventHandler() and not original event in Main():

Page 45: C#/.NET Little Pitfalls

A struct is not a classLike class, the default constructor of struct

is implicit.Unlike class, you cannot specify

implementation.Unlike class, defining other constructors

doesn’t suppress default, nor can you make it non-visible.

Page 46: C#/.NET Little Pitfalls

A struct is not a classOnly create a struct when the internal

representation is very small (~16 bytes or less).Typically, we prefer value types to behave like

primitives in that they can be treated as a single value.

The best uses of struct tend to be in creating small immutable values, due to value type copy semantics.

Avoid event definitions in struct.Do not attempt to hide default constructor for a

struct, you can’t.

Page 47: C#/.NET Little Pitfalls

Initialization OrderWhen you create a class hierarchy and then

construct a sub-class, in what order are the objects constructed?

The answer? It depends on your language…Given this little class that simply logs a message on

construction, which we’ll use to show initialization:

Let’s look at an example.

Page 48: C#/.NET Little Pitfalls

Initialization OrderConsider this base class for an abstract Shape:

Note that we are initializing a field (LogMe() will write a message to console).

Page 49: C#/.NET Little Pitfalls

Initialization OrderNow consider this sub-class for a concrete

Rectangle:

Page 50: C#/.NET Little Pitfalls

Initialization OrderWhat happens if we construct a

Rectangle(3,5) in C#?

But if we translate the same code to VB.NET?

Page 51: C#/.NET Little Pitfalls

Initialization OrderYou should know the differences in initialization

order, but also strive not to write code dependent on it:C#:

Sub-class initialization Base-class initialization Base-class constructor Sub-class constructor

VB.NET: Base-class initialization Base-class constructor Sub-class initialization Sub-class constructor

Page 52: C#/.NET Little Pitfalls

Premature polymorphismConsider again the Shape and Rectangle

puzzle from the previous pitfall. Let’s alter the Shape class to call a

polymorphic (virtual or abstract) method from the constructor:

Page 53: C#/.NET Little Pitfalls

Premature polymorphismRemembering that Rectangle looks like this:

Page 54: C#/.NET Little Pitfalls

Premature polymorphismSo what happens if we construct a new

Rectangle(3,5), remembering that now the Shape default constructor will perform a polymorphic draw?

This will compile successfully, but what will the result be?

Page 55: C#/.NET Little Pitfalls

Premature polymorphismYou 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.

This means it will call the Rectangle.Draw() method.

But there’s a problem, Rectangle’s constructor has not run yet, thus _length and _width are unassigned:

Page 56: C#/.NET Little Pitfalls

Premature polymorphismPolymorphic calls in base class constructors

are problematic since the sub-class constructor has not yet been executed, which may have class in inconsistent state.

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.

Safer to just avoid polymorphic calls in a base class constructor altogether.

Page 57: C#/.NET Little Pitfalls

Unconstrained genericsWhen you create a generic, you specify a

generic type parameter.You can constrain the generic type parameter

to specify it must be of a certain type.Narrowing allows you to treat variables of

the generic type to act like the narrowed type, thus exposing their members.

Page 58: C#/.NET Little Pitfalls

Unconstrained genericsLet’s constraint a generic that it must be a

reference type (class) but not constrain it’s type:

What does that mean if we do this?

What’s the output given T is type string?

Page 59: C#/.NET Little Pitfalls

Unconstrained genericsThe result was false!This is because the == comparison between the

unconstrained type T assumes object.Thus even though T is type string in our usage, when

the generic is compiled, it is compiled assuming object.

This means that object’s == is invoked and not string’s.

Polymorphic overrides work as you’d expect, but for hides and operator overloads, the resolution depends on the type constraint (object if unconstrained).

Page 60: C#/.NET Little Pitfalls

Unconstrained genericsRemember that a generic type parameter

that is not constrained to a specific base-type or interface is considered to be constrained to object implicitly.

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.

Page 61: C#/.NET Little Pitfalls

Conclusion.NET is an excellent development platform offering:

Great execution performance.Ease of development.Quick time to market.Fewer bugs and memory leaks.

There are a few interesting features of the language that if not understood can lead to pitfalls.

Knowing those pitfalls, why they occur, and how to avoid them are key strengths for .NET developers.

Page 62: C#/.NET Little Pitfalls

Questions?

Page 63: C#/.NET Little Pitfalls