Don Box is back from vacation in LA to do a post about generic delegates showing another great use of delegates. With .NET 1.1 I had to write a lot of different delegates because of different types. That's no longer necessary with delegates, as can be seen in Don's post.
public delegate void EventHander<S, T>(S sender, T args) where T : EventArgs;
also shows a big difference to the template implementation of C++. With C# generics it is possible to define requirements of generic types, such as T needs to be of type of EventArgs (or derived from this class). With C++ templates we just find out that a type doesn't fulfill the requirements when compiling the template using the specific type. Often the compiler error messages with many, many lines are not easy to read.
With my blog about generic methods yesterday, a comment was made that the Active Template Library (ATL) is too complex, and only hackers are able to deal with this complexity.
I loved the ATL and its flexibility. It is complex because of the complexity of C++ and the complexity of COM. ATL deals with QueryInterface, COM factories, reference counts, apartments, aggregation, containment, many ActiveX control interfaces…… OK, too complex ;-) Since Visual Studio .NET 2002, ATL offers attributes that make it easier working with the ATL. However, I still prefer doing it the old way in my few ATL projects.
Generics: don't be afraid of the complexity of generics. With C# already a lot features are easier to deal with compared to C++. One example is operator overloads. With C++ operator overloads are either instance members or need to be static members. This is dependent on the operator. So, the left side operand either is this, or the first argument of the overloaded operator. With C#, all operator overloads are static. The left and right operands are always the first and second argument. Also, with C# if the operator + is implemented, it is not necessary to implement the operator +=. This is automatically evaluated to = and +. This is different to C++. I can give you many more examples where C# makes it easier to C++.
Also, ** and & are usually not needed, although these operators are available (and sometimes needed) and can be used. Phil hopes in the comment that generics will be easier to read - at least there is no **&ppVal. :-)
I agree; I'm sure that generics will be easier to deal with compared to C++ templates.
I just got a question about ADO.NET concurrency. Here is my summary :-)
ADO.NET uses optimistic concurrency by default. The data-wizard generated code for an update statement checks for changes of the records by comparing the current values in the database with the original values inside the DataSet using a WHERE clause. Of course only the columns that are in the SELECT statement can be compared. In case another user changed a record before the update is invoked, an exception is thrown.
If the wizard is not used, the UPDATE statement must be programmed accordingly. Of course it is also possible to ignore all changes that happened from the time reading the data - just by doing a simpler UPDATE statement.
A simpler UPDATE statement with the same results as the data-wizard generated code can be done by using a timestamp-column where it is only needed to compare the timestamp-column of the original data.
With the default behavior it is important to mention if multiple records are updated, partial updates can happen. Before the conflict happens, the records are updated successfully, but because an exception is thrown, following records don't get updated even if there is no conflict. Of course this behavior can be changed.
If all or nothing should be updated, a transaction can be started before the DataAdapter.Update, and committed on success.
If all records without a conflict should be updated, there is a property in the DataAdapter, so that no exception is thrown: setting ContinueUpdateOnError to true does not generate exceptions with concurrency errors. Instead, the HasErrors and RowError properties of the DataSet and DataRow can be checked.
If pessimistic concurrency is needed, this is also possible (but should be avoided in distributed solutions).
Placing locks on the database can be done by starting a transaction before reading the data and filling the DataSet, and committing the transaction after invoking the update command. User interaction shouldn't happen in between, because here the transaction timeout can occur. Of course this is not scalable, and such a model be avoided. Until now I haven't seen such a scenario where DataSets would be useful.
Another approach is by using virtual locks: recording custom information into the database who has "locked" a record, and when. Before reading the data to do an update, the lock is checked. With this approach users can be grouped, and a supervisor may delete such virtual locks.
Using virtual locks is not useful in a disconnected scenario.
Optimistic concurrency usually is the best way updating data in a distributed, scalable solution.