more little wonders of c#/.net
DESCRIPTION
We've all seen the big "macro" features in .NET, this presentation is to give praise to the "Little Wonders" of .NET -- those little items in the framework that make life as a developer that much easier!TRANSCRIPT
More “Little Wonders” of C#/.Net
James Michael Hare2012 Visual C# MVPApplication Architect
ScottradeAugust 3rd, 2012
http://www.BlackRabbitCoder.net Twitter: @BlkRabbitCoder
Me: Blog:
http://www.BlackRabbitCoder.net Twitter: @BlkRabbitCoder
Information on Scottrade Careers: http://jobs.scottrade.com Twitter: @scottradejobs
Contact Information
The .NET Framework is full of “macro-sized” goodness that can help make our coding lives easier by automating common tasks.
But, the .NET Framework also has a lot of smaller “micro-sized” tips and tricks that can improve code.
Many developers know of most of these, but it is often surprising how many times newer developers don’t.
This presentation picks up where the first “Little Wonders” presentation leaves off…
What are “Little Wonders”?
Basically, by employing these small items at the right time, you can increase application: Readability – some of the wonders make code
much more concise and easy to read. Maintainability – often goes hand and hand with
readability, by removing ambiguity of the code, it is easier to maintain without introducing errors.
Performance – a few of the little wonders can even help increase the performance of your code (depending on usage).
How do they help?
More Little Wonders
Compiler Candy Optional/Named
Arguments Chained Constructors Generic Constraints Anonymous types
BCL Classes Enum Nullable<T> Lazy<T> Tuple Interlocked
Strings Joining Constructing
Generic Delegates Action Func
Enumerable Static Methods Empty Repeat
Collection Generators ToArray, ToList, etc.
Optional arguments allow you to specify a default value to be used if argument isn’t provided.
Default values must be compile-time constants: Numeric constant expressions String constant expressions null
Can be used to replace redundant overloads when the only purpose is to default an argument.
Optional Arguments
This…
Can replace all of these…
The default is literally compiled in at call, so this:
Is, in effect, compiled into this:
Optional Arguments
Defaults are positional, substituted from left to right:
If you want to “skip”, used named arguments:
Named Arguments
There are a couple of potential pitfalls to be aware of: If used across assemblies, can lead to subtle errors.
If a default parameter is defined in one assembly, and used in another, must make sure both get recompiled if value changes.
Default parameters are not inherited. Defaults established in an interface or base-class do not
directly apply (and can be different from) the sub-class. Default depends on reference type, not the object type.
Default Parameters
Constructors can call each other directly, which can reduce the need for redundant code in overloads.
You could use optional arguments or initializers to reign in overloads, but each have their limitations: Optional arguments must be set to compile-
time constant expressions. Initializers aren’t suitable for immutable
classes, readonly fields, get-only properties, or items whose construction may be “heavy”.
Chained Constructors
Consider this:
Can simplify by chaining constructors:
Generics allow you to make very powerful types and algorithms, but can be a two-edged sword.
An unconstrained generic type parameter makes no assumptions, giving you the lowest common denominator of all types.
Supplying a constraint reduces the types that can be used to realize the generic, but expands the things that can be done inside the generic.
Generic Constraints
For example, how would unconstrained T handle null?
Unconstrained T can be compared to null, but this is always false for value types (except Nullable<T>).
Unconstrained T cannot be assigned to null, though can be assigned to default(T).
Generic Constraints
Or constrain T to be a value type (for example, since Nullable<T> can only support value types…
Generic Constraints
Generic Constraints
Or what if we want to create a new unconstrained T instance, or access any of T’s members?
Can’t, no way to know if unconstrained T supports parameter-less construction or other members.
Generic Constraints
Supplying a constraint constrains the matching types, but also expands what you can do with the generic: where T : struct – T must be a value type, must be first.
Allows T to be used in other value generics (Nullable<T>, etc.).
where T : class – T must be a reference type, must be first. Allows T to be assigned to null in generic.
where T : U – T must be, inherit, or implement U Allows T to access any public members of U in generic.
where T : new() – T must have a public parameter-less constructor, must be last Allows T to be constructed in generic.
Generic Constraints
Constraining T to things that implement IDictionary and have parameter-less constructor:
Generic Constraints
Constraining T only value types that implement IConvertible:
Generic Constraints
We can construct and initialize any type on the fly using object initialization syntax:
Anonymous Types
What if we only need a type for a very localized purpose? Why create the boilerplate code for a class
that will only be used in a small, localized section?
Most of these classes are simple POCOs, is it worth a full definition?
If you need to use a type as a key (equality, hashing), it can get heavy. Must implement Equals() and GetHashCode()
correctly.
Anonymous Types
How would we define UserDay, UserDayTotal?
Not as trivial as would think…
Anonymous types create throw-away types for you: Creates type based on name, type, order of
properties. Creates suitable Equals() and
GetHashCode(). Syntax is like object initialization, just omit
type name.
Anonymous Types
Now, much simpler… Don’t need to define UserDay or UserDayTotal Don’t need to maintain Equals() and
GetHashCode(). Doesn’t clutter up project with throw-away
types.
Anonymous Types
A few things to be aware of: Type is generated at compile time based on
properties’ names, types, order – any deviation is a different type:
Difficult to use outside of defined scope (which shouldn’t do anyway, defeats purpose).
Anonymous Types
Enum is not only the base of enums, it also has several useful static methods: IsDefined() – Determines if an enum
constant exists with the given value. HasFlag() – Determines if a given set of bits is
set, much easier to read than the typical bitwise AND.
TryParse() – Parse a string value into enum value, much lighter than Parse().
The Enum Class
Use IsDefined() to see if a value exists
And HasFlag() simplifies flag testing:
The Enum Class
The TryParse() is much lighter than Parse(), especially if you think you may not have a valid value:
The Enum Class
You can indicate an optional value easily with null for reference types, but value types always exist.
Sometimes, appropriate sentinel values don’t exist.
The Nullable<T> generic struct simulates optional values for value types.
Nullable<T> has two key properties: HasValue – True if Value has been set to a value. Value – The value if set, throws if not.
The Nullable<T> Struct
Use when you have a value type which may or may not contain a valid value.
For example, the DateTime struct’s default value isn’t very meaningful, use DateTime? Instead.
The Nullable<T> Struct
We then must test to make sure it has a value by using HasValue property.
Once we know the value exists, access Value to retrieve it.
The Nullable<T> Struct
Nullable<T> allows some syntactical shortcuts: Nullable<T> can be abreviated T?
Nullable<T> can be assigned to null
Nullable<T> can be compared with null
The Nullable<T> Struct
Some things to watch out for: Nullable<T> doesn’t save space if value not
specified, still stores a Value, it’s just not usable.
Accessing Value when HasValue is not true is an error and will throw an exception.
Math or logical operations between Nullable<T> wrapped numeric types (int, double, etc.) has caveats: Math with null yields null. Logical ordered comparison with null yields
false.
The Nullable<T> Struct
Some classes may be expensive to create, especially if they are not always needed, in these cases lazy instances are often used.
No need to create your own lazy-initialization logic anymore, Lazy<T> does it all for you.
Can either call default constructor, or a generator.
Various thread-safety modes so you can choose the level of performance and safety you require.
The Lazy<T> Class
Lazy<T>
Constructor not called until Value accessed.
If a constructor isn’t accessible, or desired, you can always use a factory method:
Lazy<T>
By default, Lazy<T> is thread-safe, but can choose:
Lazy<T>
Sometimes, you need to just throw together several values to treat as a set of values.
Tuples allow you to do this in a generic way. Similar to anonymous types, yet different:
Both are immutable and have Equals(), GetHashCode()
Anonymous types have named properties, but not type.
Tuples have generic property names, but named type. Tuple has static factory methods to easy creation.
The Tuples
The Tuples
Simple tuples are a single “layer” and have properties numbered Item1…Item7:
Get longer Tuples by using octuple, which has TRest:
Tuples
Locking to safely increment a count is heavy:
Interlocked
Allows thread-safe, highly performant manipulation of numeric values.
Much lighter than full locks for simple operations like: Increment – adds 1 to the interlocked value Decrement – subtracts 1 from the interlocked
value Exchange – swaps two values Add – adds a value to the interlocked value
Interlocked
Using interlocked, we can do the same thing, without heavy locks:
Interlocked
Have you ever seen someone construct an array of repeated char like this?
Instead, quickly create a string of a repeated character using one of string’s constructors:
Strings of Repeated Char
Many times people write code to do this:
When they can simply do a Join():
Joining Strings
The string.Join() has a lot of power Can Join any array or IEnumerable<T>:
Can join a variable argument list, including mixed types:
Joining Strings
One of the oldest patterns in the OO playbook is to create a class that is 99% complete except for one anonymous “work” method, which is typically abstract…
Generic Delegates
Like this…
Inheriting from a class simply to provide a missing method per use is often overkill and poor coupling.
Can’t easily change behavior at runtime. Bloats projects with classes that just
override.
Generic Delegates
We can instead provide behavior through a delegate. Behavior can be added with no extra subclasses
needed. There is very limited coupling between the delegate
and the class/method using it. Behavior could be changed out at runtime.
But defining a new delegate each time gets ugly:
Generic delegates can be used for most delegate needs.
Generic Delegates
Action is a generic family of delegates that take up to 16 arguments and return nothing: Action – takes zero arguments, returns
nothing Action<T> - takes one argument, returns
nothing. Action<T1, T2> - takes two arguments,
returns nothing. … Action<T1…T16> - takes 16 arguments,
returns nothing. Action cannot be used for ref or out
arguments.
The Action Delegate
Similar to Action, but Func returns a value: Func<TResult> – takes zero args, returns a TResult. Func<T, TResult> - takes one arg, returns a TResult. Func<T1, T2, TResult> - takes two args, returns a TResult.
… Func<T1…T16, TResult> - takes 16 args, returns a TResult.
Func cannot be used for ref or out arguments. Func<T, bool> is generally preferable to
Predicate<T>.
The Func Delegate
Revised Consumer:
How many times have you created an empty array or List<T> to represent an empty sequence?
Empty Enumerables
Enumerable has a static method Empty<T>() that generates a singleton empty IEnumerable<T>.
Empty Enumerable
Allows you to create a sequence of the same item a given number of times.
Doesn’t recreate the item each time, takes whatever the parameter is and creates a sequence.
For example, to create 10 random numbers:
Enumerable Repeat
Often times you’ll have a sequence and want to store in a collection to: Avoid re-generating the sequence during
multiple iterations. Convert the sequence from one sequence type
to another. Pull the sequence into a collection to avoid
deferred execution issues.
Collection Generators
There are several LINQ extension methods to generate different collections from sequences: ToArray() – stores sequence in a T[]. ToList() – stores sequence in a List<T>. ToDictionary() – transforms sequence to a
Dictionary<TKey, TValue> given selectors for TKey and TValue.
ToLookup() – transforms sequence to a Lookup<TKey, TValue> - similar to dictionary but can have repeated keys.
Collection Generators
ToArray() and ToList() are useful for: Remove effects of deferred execution. Convert a sequence to a particular kind.
ToArray and ToList
ToDictionary() converts a linear sequence to a Dictionary that maps a single key to a single value. Keys can only exist once, multiple key
instances throw. Must provide key selector, value selector
optional.
ToDictionary
ToLookup() is similar to ToDictionary(), but it maps a key to a set of values (essentially a multi-map). Again must provide key selector, value
optional.
ToLookup
Questions?
Platinum Sponsors
Silver Sponsors
Gold Sponsors