Introduction
Everybody knows the Standard Template Library provided with most C++ compilers. Here is a proposition of STL for C# generics and .NET class design.
The .NET framework already provides many containers, but I found it could be useful to find some patterns we used to practice earlier, in particular, algorithms and binders.
Using the code
The source code provided in the downloads section is a complete Visual Studio 2005 project with the solution file. The assembly can be used in any project without restriction.
Note: It is assumed that the reader is familiar with all generic classes and interfaces of the .NET framework 2.0. This is the basis for using and understanding this library.
Algorithm
- manipulation of collections is done through
IEnumerable<T>
.
- results as
IEnumerable<T>
does not use memory, and the real calculus of each item of the result is done at each iteration on it (you can see it as an evaluation tree).
- delegates allow to provide a "behind object" for context persistence on each calculus, and replace with advantages C++ functors or function (member or not) pointer templates armada.
- for finite results, avoid infinite sets. That's a best practice, a good design, and that's why such algorithms as Iota require a count parameter.
public static class Algorithms : object
{
public static IEnumerable<T>
PartialSum<T>(IEnumerable<T> values,
BinaryFunction<T, T, T> plus) ;
public static IEnumerable<T>
AdjacentDifference<T>(IEnumerable<T> values,
BinaryFunction<T, T, T> minus) ;
public static T Accumulate<T>(IEnumerable<T> v,
BinaryFunction<T, T, T> plus, T zero) ;
public static T InnerProduct<Arg1, Arg2,
T>(IEnumerable<Arg1> v0, IEnumerable<Arg2> v1,
BinaryFunction<Arg1, Arg2, T> multiplies,
BinaryFunction<T, T, T> plus, T zero) ;
public static IEnumerable<T> Transform<Arg,
T>(IEnumerable<Arg> v,
UnaryFunction<Arg, T> f) ;
public static IEnumerable<T> Transform<Arg1, Arg2,
T>(IEnumerable<Arg1> v0, IEnumerable<Arg2> v1,
BinaryFunction<Arg1, Arg2, T> f) ;
public static IEnumerable<T>
Transform<T>(IEnumerable<T> v0,
IEnumerable<T> v1, BinaryFunction<T, T, T> f) ;
public static IEnumerable<T>
Transform<T>(IEnumerable<T> v,
UnaryFunction<T, T> f) ;
public static IEnumerable<T>
RemoveIf<T>(IEnumerable<T> v,
Predicate<T> predicate) ;
public static IEnumerable<T>
FindIf<T>(IEnumerable<T> v,
Predicate<T> predicate) ;
public static IEnumerable<T>
AdjacentFind<T>(IEnumerable<T> v,
Relation<T> relation) ;
public static IEnumerable<T>
ReplaceIf<T>(IEnumerable<T> v,
Predicate<T> predicate, T value) ;
public static IEnumerable<T>
Unique<T>(IEnumerable<T> v) ;
public static bool Exist<T>(IEnumerable<T> v,
Predicate<T> predicate) ;
public static bool ForAll<T>(IEnumerable<T> v,
Predicate<T> predicate) ;
public static int Count<T>(IEnumerable<T> v) ;
public static int CountIf<T>(IEnumerable<T> v,
Predicate<T> predicate) ;
public static IEnumerable<T> Generate<T>(int count,
Generator<T> f) ;
public static IEnumerable<T> Iota<T>(int count,
BinaryFunction<T, T, T> plus, T zero,T step) ;
public static IEnumerable<T> Iota<T>(int count,
UnaryFunction<T, T> f, T zero) ;
}
Sort and merge operations are not here for the moment. Sets operations were not interesting for me because lots of work on Sets could already be found on CodeProject.
Functional
Some standard prototypes are defined through delegates. It allows developers to use objects without having to inherit some interfaces, and as we'll see later, it is not so inefficient as we think.
public delegate T Generator<T>();
public delegate T UnaryFunction<Arg, T>(Arg x);
public delegate T BinaryFunction<Arg1, Arg2, T>(Arg1 x, Arg2 y);
public delegate bool Relation<T>(T x,T y);
Generics in .NET changes some practices we used to have with C++ templates. They provide categories for atomic operations on numbers. They are not necessary, because, type like Decimal
has their own functions (add, subtract...). I noticed Double
didn't provide these functions, and I don't understood why...
public static class Plus : object ;
public static class Minus : object ;
public static class Multiplies : object ;
public static class Divides : object ;
public static class Modulus : object ;
public static class Negate : object ;
Combining some existing and "unmodifiable" functionalities without writing code is the main interest of binders. And it allows dynamic approaches. They are defined as static functions in the Binders
class.
Bind1st
x,y->f(x,y) |- y->f(a,y)
Bind2nd
x,y->f(x,y) |- x->f(x,a)
Compose
x->f(x) , x->g(x) |- x->f(g(x))
Compose2nd
x,y->f(x,y) , x->h(x) |- x,y->f(x,h(y))
Compose1st
x,y->f(x,y) , x->h(x) |- x,y->f(h(x),y)
Symetric
x,y->f(x,y) |- x,y->f(y,x)
And now, a short example to begin with:
IEnumerable<double> v0 =
new double[] { 1.1, 2.234, 3.1415 , 5.879 , 1.47896 };
IEnumerable<double> v1 =
Algorithms.Iota<double>(5, Plus.Double, 1, 0.1);
IEnumerable<double> q =
Algorithms.Transform(v0, v1, Divides.Double);
int digits = 2;
UnaryFunction<double, double> round =
Binders.Bind2nd<double, int,
double>(Math.Round, digits);
q = Algorithms.Transform<double>(q, round);
StringBuilder sb = new StringBuilder(1024);
foreach (double x in q)
sb.AppendLine(x.ToString());
There is no need to store results. It's interesting if you don't have to calculate all the values. But if needed, create a new List
. Having an in place transformation effect on all classes seem to be meaningless, but it could be wished for any Array<T>
. To me, a writer pattern is the good solution.
Points of Interest
- This sample demonstrates generics are powerful and efficient.
- The code has a high abstraction level.
History
- First version - 10/19/2005.
Frédéric DIDIER is a 4 years experienced .Net developper. He has written a sawmill complete critical-time optimization engine in C# (Log-bucking, breakdown, small log processing ,
edger ,trimmer , harvest-parc simulation, short log sorting), improving efficiency and quality of professional existing applications written with MFC, demonstrating the superirity of .Net Framework in designing classes and applications.