Day 5 Programming in C# 70-483
Index
- Introduction
- Types of "types"
- Game rules
- Generics
- Extending your types
- Boxing and then unboxing
- Change types
- What about dynamic types
- References
Introduction
This post is part of a
series of post to help you prepare the MCSD certification, particularly the certification exam 70-483, based on the book:
You will find all the code available in the following
GitHub repository. Lets talk a little bit about threads and how they work using a .Net development environment.
Types of "types"
Types help us to define which information/data can be manage in a particular object. It's a kind of blueprint to construct objects. Types:
- Value types (Enums, small data structures, something logically immutable which contains the value for a type)
- Reference types (contains and "address" where the value is contained, ie: URL)
- Pointer types (not used often)
Another structure very often used in .Net is: Enums, coming from enumeration and it's a short way to list elements which improves readability. First element has index 0 but you can change this, ie:
See how in the previous example we've created an instance of the Enum Days and within we've changed the default index to start on 1. To compare the values of the Enum a casting to byte is required due to that's the data type defined previously in the structure.
You can also decorate your enums with [Flags] attribute which allows you to define multiple values to your enum at once and more actions using
bitwise operators, ie:
There are two locations where a type can be stored in memory: the heap and the stack:
- Reference type: the value is saved in the heap, the address to the value in the stack (faster, smaller and no Garbage collector is needed)
- Value type: normally is saved in the stack.
In C# you can't inherit from System.ValueType but you can create your own value types using the struct keyword. These are very similar to objects, with methods, properties, constructors,... but you can't create an empty constructor for a struct and you don't have inheritance either.
Game rules
Always keep in mind these two principles when designing your new classes and it will allow you to make changes to code without worrying affect other systems or break anything:
- High cohesion: all related code should remain together.
- Low coupling: parts of your code should depend less as possible between them.
The following list, is a very famous list of principles to bear in mind when you are building a new system, is called the
S.O.L.I.D. list and I already talked about in
previous posts:
- S / Single responsibility principle: a class only needs to have one function, ie, a class for saving data and print data on the screen would break this rule.
- O / Open-closed principle: an object should be open for extension but closed for modification.
- L / Liskov substitution principle: a inherited class should be replaceable by the base class. Not to overwrite methods from the base class making this class useless.
- I / Interface segregation principle: use specific interfaces instead of a general interface. A user of an interface should not implement methods he's not going to use.
- D / Dependency inversion principle: when you use a service in your application, you should not depend on the implementation of that particular service, instead, you should depend on an interface or abstract class so you are less coupled to the service.
Generics
A generic type (C# v2.0) uses a Type parameter instead of hard-coding all the types. For example, in C#, you can see how generics are used in the
Nullable types. Value types and reference types can contain a null value or pointing to a reference containing a null value respectively.
But a regular boolean can be true or false. Here comes the
Nullable elements which contains a
hasValue flag and the value. Instead of creating a
Nullable type for each data type, we use generics to pass any element and get more flexibility. This way generics are used to promote code reuse. See how
Nullable class is implemented using generics:
Generics are really powerful you can use them in structs, classes, delegates, arrays, interfaces, methods and properties. You can even specify multiple generics. Now, I'm going to show you how to apply restrictions in your generic definitions by using the keyword
where, see list here:
- where T : struct, the type argument must be a value type (only Nullable is not allowed).
- where T : class, the type argument must be a reference type.
- where T : new(), the type must have a public default constructor.
- where T : <base class name>, the type argument must be or derive from the specified base class.
- where T : <interface name>, the type argument must be or implement the specified interface. Multiple interface can be specified. The constraining interface can also be generic.
- where T : U, the type argument supplied for T must be or derive from the argument supplied for U.
See an example here of using different constrains:
The default value in a reference type is null, in a value type like int for instance is 0 but what is the default value in a generic, you can see how in the previous example the use of default(T) returns the default value of the generic being used.
Extending your types
It's in C# v4.0 when a new capability was implemented to add more functionality to your types called: extension methods. Imagine the class String (your type) and for different reasons you apply always a particular function over your strings. You might want to extend the string type and provide a new method with your logic in it. It's not necessary to build your own types and you end up bringing more capability to your code. See how:
public class Product
{
public decimal Price { get; set; }
}
public static class MyExtensions
{
public static decimal Discount(this Product product)
{
return product.Price * .9M;
}
}
public static class Types6
{
public static void Run()
{
Product table = new Product();
table.Price = 100;
System.Console.WriteLine(table.Discount());
}
}
// output
// 90.0
The discount method is an extension method because is not implemented in the Product class. Notice how the Discount uses "this Product" as its first argument transforming it into a extension method. You can have extension methods on a class or struct but you can have extension methods within an interface. Interfaces don't have implementation normally, this way, you end up with methods available in every concrete implementation of the interface.
Another way of extending types is by overriding methods. You need to inherit from a class which contains methods defined as virtual, then you can copy the definition of the method supplying virtual by override keyword. You can block the override by using the keyword sealed in a class definition or within a method level.
Boxing and then unboxing
When you were working with value types, you might want to store its value within a reference type. This is commonly used to send parameters into methods and bear in mind it has some performance implications. For example, if you define a number (
int i = 3;) you can easily box it by using:
object o = i; Then, in case of unbox this object you have to perform a casting like this:
int x = (int)o;
Running an invalid unboxing operation will end up in a
InvalidCastException. For example when you use
GetType, it always boxes your value because it's defined on an object. Another example is when you save a value in an interface like
IFormattable x = 3;
Change types
Don't think we'll try to change an
Address object into a
House object, nop. We're talking about change a
int to a
double. Conversion types:
- Implicit: implicitly the compiler knows how to change types. Normally by losing precision, ex: change from int to double. A integer value fits inside of a double value. Same happens when changing base classes into derived objects.
- Explicit: a.k.a "Casting". Using the same example, casting a double to an integer you need to tell this to the compiler somehow: myInteger = (int)myDouble; And exactly the same behaviour when changing derived objects into base objects.
- User-defined: you can define implicit and explicit method to perform converstion between types using the properties within your own classes. You need to specify the return type (casting to) and the type you are casting from. See how:
- See here how to use this class:
- Both imlicit and explicit operators are executed and we end up with a value of 42. This improves the usability providing to the user of our class a direct way to convert between types.
- Helper class: conversion between noncompatible types you can use System.BitConverter. For compatible types you have the System.Convert class, and the Parse() or TryParse() methods.
This conversions between types may trigger and exception during runtime and instead of wrapping up your code with a try/catch (think in the performance) you can use a couple of expressions to safe you transformations:
is and
as. The
is returns true or false, depending on whether the conversion is allowed. The
as operator returns the converted value or null if the conversion is not possible.
if (connection is SqlConnection) { // do stuff }
MemoryStream memoryStream = stream as MemoryStream;
Using
as is more efficient when you want to use the value afterwards. If you just want to check if your value is a certain type use
is.
What about dynamic types
C# is partially static typed language. To deal with JavaScript, COM Interop, JSON or reflection in C#, the data type
dynamic was introduced. Which is a like a joker when playing cards, you don't really know which type is containing because all the type checking and necessary conversion take place at runtime. It's a bit dangerous and it may end triggering errors and the performance can be affected so think carefully when to use it.
.Net offers:
DynamicObject and
ExpandoObject. With the first one,
derived classes practically can do whatever they want, from overriding operations, calling methods or performing conversions. On the other side,
ExpandoObject is a sealed implementation that enables you to get and set properties on a type. For example, the
ViewBag in MVC which allow developers to save whatever value and send it back to the view.