Chapter 1. Prerequisites—Generics and Transactions

In This Chapter

In November 2005, Microsoft released version 2.0 of the .NET Framework. WinFX and the Windows Communication Foundation are built on top of that framework and leverage key additions to the functionality version 2.0 provided.

To get the most out of this book, you will need to have an understanding of two features: Generics and System.Transactions. This chapter provides an overview of these features, providing the prerequisite knowledge to complete the exercises in future chapters.

Note

For those wanting to go deep in these areas, it is recommended that you visit http://msdn.microsoft.com. There you can find documentation and whitepapers on these subjects.

Generics

When you design classes, you do so without a crystal ball. Classes are designed based on the available knowledge of who the consumers of the class likely could be, and the data types those developers would like to use with your class.

While developing a system, you may have a need to develop a class that stores a number of items of the integer type. To meet the requirement, you write a class named Stack that provides the capability to store and access a series of integers:

using System;

public class Stack
{
    int[] items;

    public void Push (int item) {...};
    public int Pop() {...};

}

Suppose that at some point in the future, there is a request for this same functionality, only the business case requires the support for the double type. This current class will not support that functionality, and some changes or additions need to be made.

One option would be to make another class, one that provided support for the double type:

public class DoubleStack
{
    double[] items;

    public void Push (double item) {...};
    public int Pop() {...};

}

But doing that is not ideal. It leaves you with two classes, and the need to create others if requests to support different types arise in the future.

The best case would be to have a single class that was much more flexible. In the .NET 1.x world, if you wanted to provide this flexibility, you did so using the object type:

using System;

public class Stack
{
    object[] items;

    public void Push (object item) {...};
    public int Pop() {...};

}

This approach provides flexibility, but that flexibility comes at a cost. To pop an item off the stack, you needed to use typecasting, which resulted in a performance penalty:

double d = (double)stack.Pop;

In addition, that class allowed the capability to write and compile the code that added items of multiple types, not just those of the double type.

Fortunately, with version 2.0 of the .NET Framework, Generics provide us a more palatable solution. So what are Generics? Generics are code templates that allow developers to create type-safe code without referring to specific data types.

The following code shows a new generic, GenericStack<M>. Note that we use a placeholder of M in lieu of a specific type:

public class GenericStack<M>
{
    M[] items;

    void Push(M input) { }
    public M Pop() {}
}

The following code shows the use of GenericStack<M>. Here, two instances of the class are created, stack1 and stack2. stack1 can contain integers, whereas stack2 can contain doubles. Note that in place of M, the specific type is provided when defining the variables:

class TestGenericStack
{

    static void Main()
    {
        // Declare a stack of type int
        GenericStack<int> stack1 = new GenericStack<int>();

       // Declare a list of type double
       GenericStack<double> stack2 = new GenericStack<double>();

    }
}

This provides support for us to use the functionality for the types we know we must support today, plus the capability to support types we’ll be interested in in the future. If in the future, you create a class named NewClass, you could use that with a Generic with the following code:

// Declare a list of the new type
GenericStack<NewClass> list3 = new GenericStack<NewClass>();

In GenericStack<M> class, M is what is called a generic-type parameter. The type specified when instantiating the GenericStack<M> in code is referred to as a type argument. In the code for the TestGenericStack class, int and double, are both examples of type arguments.

In the preceding example, there was only one generic-type parameter, M. This was by choice and not because of a limitation in the .NET Framework. The use of multiple generic-type parameters is valid and supported, as can be seen in this example:

class Stock<X, Y>
{

    X identifier;
    Y numberOfShares;


    ...

}

Here are two examples of how this could then be used in code. In the first instance, the identifier is an int, and the number of shares a double. In the second case, the identifier is a string, and the number of shares is an int:

Stock<int,double> x = new Stock<int,double>();
Stock<string,int> y = new Stock<string,int>();

In these examples, we’ve also used a single letter to represent our generic-type parameter. Again, this is by choice and not by requirement. In the Stock class example, X and Y could have also been IDENTIFIER and NUMBEROFSHARES.

Now that you’ve learned the basics of generics, you may have a few questions: What if I don’t want to allow just any type to be passed in, and want to make sure that only types deriving from a certain interface are used? What if I need to ensure that the type argument provided refers to a type with a public constructor? Can I use generic types in method definitions?

The good news is that the answer to each of those questions is yes.

There will undoubtedly be instances in which you will want to restrict the type arguments that a developer may use with the class. If, in the case of the Stock<X, Y> generic, you wanted to ensure that the type parameter provided for X was derived from IStockIdentifier, you could specify this, as shown here:

public class Stock<X, Y> where X:IStockIdentifier

Although this example uses a single constraint that X must derive from IStockIdentifier, additional constraints can be specified for X, as well as Y.

In regard to other constraints for X, you may want to ensure that the type provided has a public default constructor. In that way if your class has a variable of type X that creates a new instance of it in code, it will be supported.

By defining the class as

public class Stock<X, Y> where X:new()

you ensure that you can create a new instance of X within the class:

X identifier = new X();

As mentioned earlier, you can specify multiple constraints. The following is an example of how to specify that the Stock class is to implement both of the constraints listed previously:

public class Stock<X, Y> where X:IStockIdentifier, new()

In the GenericStack<M> example, you saw that there were Push and Pop methods:

public class GenericStack<M>
{
    M[] items;

    void Push(M input) { }
    public M Pop() {}
}

Push and Pop are examples of generic methods. In that code, the generic methods are inside of generic types. It is important to note that they can also be used outside of generic types, as seen here:

public class Widget
{
    public void WidgetAction<M>(M m)
    {...}
}

Note

It should be noted that attempts to cast a type parameter to the type of a specific class will not compile. This is in keeping with the purpose of generics—casting to a specific type of class would imply that the generic-type paremeter is, in fact, not generic. You may cast a type parameter, but only to an interface.

Inheritance is the last area we’ll cover on Generics in this chapter. Inheritance is handled in a very straightforward manner—when defining a new class that inherits from a generic, you must specify the type argument(s).

If we were to create a class that inherited from our Stock class, it would resemble the following code:

public class Stock<X,Y>
{...}
public class MyStock : Stock<string,int>
{...}

System.Transactions

The definition of a transaction is “an action or activity involving two parties or things that reciprocally affect or influence each other.” In developing software systems, there is regularly the need to have transactional behavior.

The canonical example for a software transaction is that of transferring funds. In that scenario, you are decrementing money from a savings account and crediting money to a checking account. If an issue were to occur sometime after decrementing the money from your savings account but before adding it to your checking account, you would expect the transaction to roll back. Rolling back the transaction would place the funds back in your savings account.

The topic of transactions and transactional messaging is discussed in Chapter 5, “Reliable Sessions, Transactions, and Queues,” but before you go to that chapter, it will be helpful to have an understanding of the added functionality around transactions in version 2.0 of the framework, specifically the functionality exposed via System.Transactions.

This next section begins with an overview of how transactions are managed in the .NET Framework, and then discusses how to implement transactions in code.

Transaction Managers

Version 2.0 of the framework introduced two new transaction managers: the Lightweight Transaction Manager and the OleTx Transaction Manager.

The Lightweight Transaction Manager (LTM) is a two-phase commit coordinator that provides transaction management within a single app domain. It is used for scenarios in which there is only a single durable resource involved, and there is no requirement for the Transaction Manager to log. This offers the capability to incorporate transaction management into an application without Microsoft Distributed Transaction Coordinator (DTC) and the overhead associated with it.

The OleTx Transaction Manager can handle those scenarios not handled by the LTM. The OleTx Transaction Manager is used for transactions that involve more than one durable resource, or span beyond a single app domain.

As a developer, you will not interact with the transaction managers directly, but instead through the objects in the System.Transactions namespace. One of the key benefits of this approach is the capability to support transaction promotion.

By default, every System.Transactions transaction begins utilizing the Lightweight Transaction Manager. If there comes a point in the scope of the transaction where it involves a second durable resource, that is, a second database connection, or crosses the app domain boundary, it is promoted and enlisted in a new OleTx transaction. This lets you, as a developer, focus on writing transactions, and lets the .NET framework use the best manager for a given scenario.

Coding System.Transactions Transactions

To utilize System.Transactions in your solutions, there are two ways to go about the task: by defining them declaratively using attributes or by explicitly defining them in code.

Declarative Coding of Transactions

If you’ve developed using Enterprise Services in the past, you will already be familiar with adding transactions in a declarative way. Declarative coding for transactions is done by applying attributes.

The following code is an example from an Enterprise Services application. In it you can see that two attributes are assigned, one at the class level and one at the operation level:

[Transaction(TransactionOption.Required)]
public class WireTransfer : ServicedComponent
{
    [AutoComplete]
    public double Transfer(int sourceAccountNbr, int targetAccountNbr, double amount);
    {
        ...
    }
}

At the class level, the [Transaction] attribute is applied. This serves as an instruction indicating that calls to any method within the class will occur within a transactional context.

At the operation level, the [AutoComplete] attribute is placed on the Transfer method. This serves as an instruction to automatically commit a transaction if the Transfer method does not end in error.

Explicit Coding of Transactions

System.Transactions also provides the capability to explicitly code transactions. Transactions will occur within a TransactionScope. TransactionScope provides a definition of the boundaries of the transaction, with all the code contained within it becoming part of the transaction:

using (TransactionScope scope = new TransactionScope ())
{
     //Code that will be part of the transaction
}

In the preceding code, the transaction is assumed complete when the using statement has been completed. To explicitly state that the scope was completed, use the Complete method on the TransactionScope object:

using (TransactionScope scope = new TransactionScope ())
{
     //Code that will be part of the transaction
     scope.Complete();
}

Transaction scopes can be nested, and when a TransactionScope is defined, there is the capability to specify how this scope will work with any ambient transactions. For example, you may want to utilize an ambient transaction, or you may demand that a new transaction be created for a particular scope of work—regardless of any existing transactions. You may even want to suppress the ambient transaction context for this scope of work.

You can introduce this when defining your scope, by specifying the appropriate value of the TransactionScopeOption enumeration. In the following listing, you can see an example in which there are nested transaction scopes and the innermost scope requires that it be part of a new transaction:

using (TransactionScope scope = new TransactionScope (TransactionScopeOption.Required))
{
     //Code that will be part of the transaction
     using (TransactionScope scope = new TransactionScope (TransactionScopeOption.
Explicit Coding of TransactionsRequiresNew))
     {
         //More work that exists inside of a new transaction;
     }

}

You can also specify additional options, such as isolation level or timeout, by passing in an instance of the TransactionOptions class to the TransactionScope constructor. This provides the capability to specify values for things such as isolation level and timeout. The next listing shows the setting of the isolation level of the transaction to ReadCommitted:

public double Transfer(int sourceAccountNbr, int targetAccountNbr, double amount)
{
        TransactionOptions options = new TransactionOptions();
        options.IsolationLevel = IsolationLevel.ReadCommitted;
        using (TransactionScope scope = new TransactionScope (TransactionScopeOption.
Explicit Coding of TransactionsRequired, options))
        {
              //Code that will be part of the transaction
        }
}

Summary

In this chapter you were introduced to two key pieces of functionality in the second version of the .NET Framework: Generics and System.Transactions.

If you were unfamiliar with Generics before starting the chapter, you should now have an understanding of what they are, why they’re important, and how they can be used. Generics are very visible in WCF, and you’ll see quite a bit of them in the following chapters.

You should now also have an understanding of System.Transactions in the framework. The Lightweight Transaction Manager and the OleTx Transaction Manager were introduced, as was how to write transactional code. This will be helpful as you navigate through Chapter 5, which covers transactions within the context of WCF.

With these two prerequisite areas behind you, you’re now ready to learn the ABC’s of the Windows Communication Foundation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.2.157