SlideShare a Scribd company logo
James Michael Hare 2011 Visual C# MVP Application Architect Scottrade August 5 th , 2011 Blog: http://www.BlackRabbitCoder.net Twitter: @BlkRabbitCoder
The joys of managed code Modern programming languages are extremely advanced and give us great frameworks that allow us to develop software more easily: Higher levels of abstraction Managed Memory / Garbage Collection Better management of resources Stronger type safety Processor / Platform independence Shorter learning curve Faster time to market
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.
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 .
Converting boxed values We 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)
Creating unnecessary strings If building string over multiple statements, don’t do: But do: Latter is 100x faster (literally) and creates less garbage.
Creating unnecessary strings When 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.
Creating unnecessary strings When 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.
Nullable<T>  arithmetic Value 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.
Nullable<T>  arithmetic To 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.
Nullable<T>  arithmetic For example, let’s say you have:
Nullable<T>  arithmetic And you perform: What is the result? Does it compile? Does it throw at runtime? Does it succeed?
Nullable<T>  arithmetic It 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 ”.
Nullable<T>  arithmetic 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:
Nullable<T>  comparisons A 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 <= :
Nullable<T>  comparisons Note that this means you can not rely on logic ladders like this for  Nullable<T > wrapped types:
Nullable<T>  comparisons Remember 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.
Mutable  readonly  values readonly  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.
Mutable  readonly  values This 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:
Mutable  readonly  values Is 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.
Mutable  readonly  values Must 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: String ReadOnlyCollection
const  values are  compile-time readonly  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.
const  values are  compile-time Consider this class in a class library  Order.DLL : Now, we use this in a program,  OrderServer.EXE:
const  values are  compile-time If we run this, we get the results we expect: Max parts per order is:  2 Max 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:  2 Max 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.
const  values are  compile-time 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. 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.
Operators are  overloaded Operators 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.
Operators are  overloaded The 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:
Operators are  overloaded What 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.
Operators are  overloaded Remember 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).
Hiding is the default 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: 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.
Hiding is the default The default behavior is to  hide , not  override  even if base-class member declared  virtual  or  override:
Hiding is the default This 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.
Hiding is the default Watch 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:
Default parameters Can 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 )
Default parameters The 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.
Default parameters Consider:
Default parameters What happens: Because resolved at compile-time, we will get: SubTag BaseTag ITag
Default parameters Avoid 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.
A  struct  is not a  class In 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.
A  struct  is not a  class 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: 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.
A  struct  is not a  class Assuming: What happens:
A  struct  is not a  class A  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.
A  struct  is not a  class If you subscribe to a copy you do not subscribe to original.
A  struct  is not a  class This will have no output, because subscribed to copy of event in  AddEventHandler()  and not original event in  Main() :
A  struct  is not a  class Like  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.
A  struct  is not a  class Only 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.
Initialization Order When 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.
Initialization Order Consider this base class for an abstract  Shape: Note that we are initializing a field ( LogMe()  will write a message to console).
Initialization Order Now consider this sub-class for a concrete  Rectangle:
Initialization Order What happens if we construct a  Rectangle(3,5)  in C#? But if we translate the same code to VB.NET?
Initialization Order You 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
Premature polymorphism Consider 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:
Premature polymorphism Remembering that  Rectangle  looks like this:
Premature polymorphism So 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?
Premature polymorphism 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 . 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:
Premature polymorphism 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. 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.
Unconstrained generics When 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.
Unconstrained generics Let’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 ?
Unconstrained generics The 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).
Unconstrained generics 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. 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.
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.
Questions?
 

More Related Content

PPTX
Introduction to CSS
PPT
C#/.NET Little Wonders
PDF
Developing SAP Integration services in IBM BPM Advanced
PDF
Lesson-07.pdf
PPTX
Algorithm and flowchart
PPT
Web servers
PDF
Django Tutorial | Django Web Development With Python | Django Training and Ce...
Introduction to CSS
C#/.NET Little Wonders
Developing SAP Integration services in IBM BPM Advanced
Lesson-07.pdf
Algorithm and flowchart
Web servers
Django Tutorial | Django Web Development With Python | Django Training and Ce...

What's hot (20)

PPT
Generations Of Programming Languages
PDF
Git best practices workshop
PPT
Grails Controllers
DOCX
Common dialog control
PDF
github-actions.pdf
PPTX
Database change management with Liquibase
PPTX
Continuous DB Changes Delivery With Liquibase
PPTX
Windows form application - C# Training
PPTX
Web development Platform Constraints.pptx
PPTX
Server Side Programming
PPTX
Coding standard and coding guideline
PPTX
Basic html structure
PPTX
C# coding standards, good programming principles & refactoring
PPTX
Bugzilla
PPTX
Técnicas para evitar el plagio
PDF
MySQL Workbench Tutorial | Introduction To MySQL Workbench | MySQL DBA Traini...
PDF
Clean Architecture in Golang
PPS
Introduction to Mysql
PDF
Up to Speed on HTML 5 and CSS 3
Generations Of Programming Languages
Git best practices workshop
Grails Controllers
Common dialog control
github-actions.pdf
Database change management with Liquibase
Continuous DB Changes Delivery With Liquibase
Windows form application - C# Training
Web development Platform Constraints.pptx
Server Side Programming
Coding standard and coding guideline
Basic html structure
C# coding standards, good programming principles & refactoring
Bugzilla
Técnicas para evitar el plagio
MySQL Workbench Tutorial | Introduction To MySQL Workbench | MySQL DBA Traini...
Clean Architecture in Golang
Introduction to Mysql
Up to Speed on HTML 5 and CSS 3
Ad

Similar to C#/.NET Little Pitfalls (20)

PPT
Csharp4 operators and_casts
PPT
Classes2
PDF
C# Summer course - Lecture 4
PPTX
C sharp part 001
PPT
Introduction To C#
PPT
20. Object-Oriented Programming Fundamental Principles
PPTX
PDF
Whats Newi in C# 8.0
PPTX
PDF
Assign
PPT
Basic c#
PPTX
CSharp presentation and software developement
PPTX
03. operators and-expressions
PPTX
3 operators-expressions-and-statements-120712073351-phpapp01
PPTX
3 operators-expressions-and-statements-120712073351-phpapp01
PDF
Sony C#/.NET component set analysis
PPTX
03. Operators Expressions and statements
PDF
C Sharp: Basic to Intermediate Part 01
Csharp4 operators and_casts
Classes2
C# Summer course - Lecture 4
C sharp part 001
Introduction To C#
20. Object-Oriented Programming Fundamental Principles
Whats Newi in C# 8.0
Assign
Basic c#
CSharp presentation and software developement
03. operators and-expressions
3 operators-expressions-and-statements-120712073351-phpapp01
3 operators-expressions-and-statements-120712073351-phpapp01
Sony C#/.NET component set analysis
03. Operators Expressions and statements
C Sharp: Basic to Intermediate Part 01
Ad

Recently uploaded (20)

PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
CIFDAQ's Market Insight: SEC Turns Pro Crypto
PDF
Spectral efficient network and resource selection model in 5G networks
PPTX
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
PDF
Shreyas Phanse Resume: Experienced Backend Engineer | Java • Spring Boot • Ka...
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PDF
Empathic Computing: Creating Shared Understanding
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Modernizing your data center with Dell and AMD
PDF
KodekX | Application Modernization Development
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Encapsulation theory and applications.pdf
Advanced methodologies resolving dimensionality complications for autism neur...
CIFDAQ's Market Insight: SEC Turns Pro Crypto
Spectral efficient network and resource selection model in 5G networks
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Network Security Unit 5.pdf for BCA BBA.
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
Shreyas Phanse Resume: Experienced Backend Engineer | Java • Spring Boot • Ka...
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Empathic Computing: Creating Shared Understanding
Mobile App Security Testing_ A Comprehensive Guide.pdf
Modernizing your data center with Dell and AMD
KodekX | Application Modernization Development
Building Integrated photovoltaic BIPV_UPV.pdf
Chapter 3 Spatial Domain Image Processing.pdf
Diabetes mellitus diagnosis method based random forest with bat algorithm
Dropbox Q2 2025 Financial Results & Investor Presentation
Encapsulation theory and applications.pdf

C#/.NET Little Pitfalls

  • 1. James Michael Hare 2011 Visual C# MVP Application Architect Scottrade August 5 th , 2011 Blog: http://www.BlackRabbitCoder.net Twitter: @BlkRabbitCoder
  • 2. The joys of managed code Modern programming languages are extremely advanced and give us great frameworks that allow us to develop software more easily: Higher levels of abstraction Managed Memory / Garbage Collection Better management of resources Stronger type safety Processor / Platform independence Shorter learning curve Faster time to market
  • 3. 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.
  • 4. 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 .
  • 5. Converting boxed values We 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)
  • 6. Creating unnecessary strings If building string over multiple statements, don’t do: But do: Latter is 100x faster (literally) and creates less garbage.
  • 7. Creating unnecessary strings When 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.
  • 8. Creating unnecessary strings When 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.
  • 9. Nullable<T> arithmetic Value 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.
  • 10. Nullable<T> arithmetic To 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.
  • 11. Nullable<T> arithmetic For example, let’s say you have:
  • 12. Nullable<T> arithmetic And you perform: What is the result? Does it compile? Does it throw at runtime? Does it succeed?
  • 13. Nullable<T> arithmetic It 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 ”.
  • 14. Nullable<T> arithmetic 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:
  • 15. Nullable<T> comparisons A 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 <= :
  • 16. Nullable<T> comparisons Note that this means you can not rely on logic ladders like this for Nullable<T > wrapped types:
  • 17. Nullable<T> comparisons Remember 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.
  • 18. Mutable readonly values readonly 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.
  • 19. Mutable readonly values This 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:
  • 20. Mutable readonly values Is 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.
  • 21. Mutable readonly values Must 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: String ReadOnlyCollection
  • 22. const values are compile-time readonly 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.
  • 23. const values are compile-time Consider this class in a class library Order.DLL : Now, we use this in a program, OrderServer.EXE:
  • 24. const values are compile-time If we run this, we get the results we expect: Max parts per order is: 2 Max 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: 2 Max 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.
  • 25. const values are compile-time 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. 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.
  • 26. Operators are overloaded Operators 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.
  • 27. Operators are overloaded The 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:
  • 28. Operators are overloaded What 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.
  • 29. Operators are overloaded Remember 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).
  • 30. Hiding is the default 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: 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.
  • 31. Hiding is the default The default behavior is to hide , not override even if base-class member declared virtual or override:
  • 32. Hiding is the default This 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.
  • 33. Hiding is the default Watch 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:
  • 34. Default parameters Can 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 )
  • 35. Default parameters The 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.
  • 37. Default parameters What happens: Because resolved at compile-time, we will get: SubTag BaseTag ITag
  • 38. Default parameters Avoid 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.
  • 39. A struct is not a class In 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.
  • 40. A struct is not a class 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: 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.
  • 41. A struct is not a class Assuming: What happens:
  • 42. A struct is not a class A 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.
  • 43. A struct is not a class If you subscribe to a copy you do not subscribe to original.
  • 44. A struct is not a class This will have no output, because subscribed to copy of event in AddEventHandler() and not original event in Main() :
  • 45. A struct is not a class Like 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.
  • 46. A struct is not a class Only 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.
  • 47. Initialization Order When 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.
  • 48. Initialization Order Consider this base class for an abstract Shape: Note that we are initializing a field ( LogMe() will write a message to console).
  • 49. Initialization Order Now consider this sub-class for a concrete Rectangle:
  • 50. Initialization Order What happens if we construct a Rectangle(3,5) in C#? But if we translate the same code to VB.NET?
  • 51. Initialization Order You 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
  • 52. Premature polymorphism Consider 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:
  • 53. Premature polymorphism Remembering that Rectangle looks like this:
  • 54. Premature polymorphism So 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?
  • 55. Premature polymorphism 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 . 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:
  • 56. Premature polymorphism 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. 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.
  • 57. Unconstrained generics When 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.
  • 58. Unconstrained generics Let’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 ?
  • 59. Unconstrained generics The 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).
  • 60. Unconstrained generics 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. 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.
  • 61. 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.
  • 63.