|
I'm personally a big fan of Spec#. If you look closely at the API, you'll notice I try to follow Spec# by using it's keywords requires and ensures . Spec# however, is currently a research project and it will probably not be released in the near future. As long as Spec# isn't released, many of us who work on commercial project, won't be able to use Spec# and language supported DbC features in their projects. And even when it’s released, many of us still won’t be able to benefit from it for a long time, because the switch to another programming language won’t happen over day.
As you note, CuttingEdge.Conditions isn't a Design By Contract framework, and it’s not my intend to go this way with the framework. If you really want this, you can look at the LinFu.DesignByContract2 framework, or use a language like Spec# or Eiffel.NET.
http://www.cuttingedge.it/blogs/steven/
modified on Saturday, September 6, 2008 12:32 PM
|
|
|
|
|
fair enough, that makes sense.
I know Spec# is still in research and doesn't have a LIVE licence yet, but what you can do is use the spec# contracts in C# code (in comments, using post-compilation). However that is not very kosher so you've got a good point.
|
|
|
|
|
Have you seen LinFu[^]? How is your library different/better than LinFu?
|
|
|
|
|
Hi Judah,
Thank you for pointing at LinFu. The Design by Contract part of the LinFu library is very interesting and Philip Laureano has written a great article (I rewarded the article with a vote of 5). Both CuttingEdge.Conditions and LinFu.DesignByContract2 will give you the same result, but they differ quite a bit in getting to that result.
They quite look a like in a way that both frameworks are trying to get Design by Contract (DbC) to the mainstream programmer. Both frameworks can be used in defining preconditions and postconditions. LinFu also has the possibility to define invariants invariants . With CuttingEdge.Conditions you can’t (yet. I'm considering this!). I'd like to see DbC as a compiler verified mechanism, so in that sense I believe neither of them really do DbC (I rather see them as defensive programming tools). Both are only able to verify the checks during runtime (yes, that's why we really need Spec#).
The big difference between the two is how developers should work with it. One of the main requirements for CuttingEdge.Conditions, is that the barrier for developers in using the framework should be as low as possible. CuttingEdge.Conditions should be as easy to use as possible. LinFu is a great framework, but it isn't easy. I like how it uses [attributes] in defining the contracts, but it requires a lot of code to define even the simplest validations. Besides that, it uses an Inversion of Control (IoC) mechanism to enable wrapping the real objects with proxies to enable the validation. IoC is great, but it depends on the project’s needs and the skill of the developers if you’re willing to walk that path. Besides that, the code you write has to be designed for LinFu (read: everything must be virtual / overridable or have an interface).
Another difference between the two is that CuttingEdge.Conditions plays by the rules of the Framework Design Guidelines (FDG) by always throwing an ArgumentException when a precondition fails. LinFu.DesignByContract2 throws a PreconditionException . This allows you to use CuttingEdge.Conditions for reusable frameworks, while LinFu.DesignByContract2 is limited to internal libraries and end user applications (when playing to the rules of the FDG, that is).
By using attributes to define the contracts, LinFu.DesignByContract2 uses in fact Aspect Oriented Programming (AOP). This allows you to keep your code very clean and even add contracts later on (without altering your code), or even at runtime. It’s even possible to add validation to code you don’t own (still this support is limited, because everything you want to validate, must be virtual or have an interface). These are all things that simply can’t be done with CuttingEdge.Conditions.
CuttingEdge.Conditions only supports validations to be written inside the method that has to be validated. With LinFu, validations can be written everywhere, except within the method that has to be validated. This is a big difference, that has to be taken into consideration. Of course, from an AOP perspective, it wouldn’t make sense to define those validations inside the method itself.
LinFu also has a more natural way of writing validations, a bit like CuttingEdge.Conditions does. But, because LinFu doesn’t allow you to write those validations within the actual method, those two are actually hard to compare. Still, the two examples below, could give you some idea in how the syntaxes look on the two frameworks, while doing the same validation:
LinFu Example on C# 3.0:
Ensure.On(contract)
.ForMethodWith(m => m.Name == "Open")
.That(c => c.State == ConnectionState.Open)
.OtherwisePrint("The connection failed to open!");
CuttingEdge.Conditions on C# 3.0:
Condition.Ensures(connection.State)
.IsEqualTo(ConnectionState.Open, "The connection failed to open!");
Please don't get me wrong. I think LinFu is great and it is much more powerful than CuttingEdge.Conditions will ever be, and again, I really like the declarative way of defining contracts with attributes! However, adding LinFu.DesignByContract2 to a project is a major design decision and can't be taken lightly, while adding CuttingEdge.Conditions is practically for free, but is more limited in use and doesn't play by the rules of IoC and AOP.
I’ve told you how the two frameworks are different. Which one is better on the project you work on is for you to decide.
|
|
|
|
|
Hi!
I like especially the opportunity to concatenize validators.
to Topic (idea):
I run the Source and found, that the debugger stops where the exeptions are thrown (deep inside of CuttingEdge.dll).
So I applied the [System.Diagnostics.DebuggerStepThrough] - attribute to CuttingEdge.Conditions.ValidatorExtensions.Contains() and to CuttingEdge.Conditions.Throw.StringShouldContain() .
After that the Debugger stopped on my exception causing line:
"lkjd".Requires("ID2").Contains("kjm");
(Maybe thats not nessesary, if your Dll is installed in gac, I didn't try that out.)
|
|
|
|
|
I don't really understand, why you set up all these valitations as Extension functions.
I suppose, it could be done by member-functions of the Validator-class just as well.
Maybe a little class-hirarchy, like:
Validator<T> - StringValidator
\
CollectionValidator<TCollection>
(To me) that would be a more clear design (e.g. easyer to browse in objectbrowser) than that huge static class full of extension functions.
|
|
|
|
|
Thank you for downloading and running the framework. I appreciate that. I'll answer both questions here:
1. Why not use the [DebuggerStepThrough] attribute.
I considered using that attribute, but I expect users of the library to simply include the DLL to their projects. You saw the debugger stopping deap inside CuttingEdge.Conditions’ code. This has actually nothing to do with not installing it into the GAC. This actually happened because Visual Studio found the .pdb file next to the included assembly. Try removing the .pdb from the \bin\Release directory of the CuttingEdge.Conditions project, and recompile your client project. You'll see that the debugger now breaks at your own code.
If it’s really a problem that the framework’s methods aren’t decorated with [DebuggerStepThrough] , please mail me (this holds for everybody reading this). I might reconsider.
2. Why use the less convenient extension methods, instead of instance methods.
There are a lot of methods that simply can't be expressed as instance method, because of the generic type constrains on them (you downloaded the source, I challenge you to try ). Some of them could indeed be expressed as instance method, but then users have to browse two places to find all methods. This would actually be worse. That's why I chose to define all validations as extension method.
Thanks again for checking it out!
http://www.cuttingedge.it/blogs/steven/
|
|
|
|
|
Hi!
The .NET Junkie wrote: 1 [...] Try removing the .pdb from the \bin\Release directory of the CuttingEdge.Conditions project, and recompile your client project.
I did so, then started with F5, but VS is too clever for me .
Although I created a new clean Solution - same result
The .NET Junkie wrote: 2 [...] There are a lot of methods that simply can't be expressed as instance method
can you give me a sample, please?
The .NET Junkie wrote: [...] I challenge you to try
Sorry that I didnt examinate all these 95 functions yet
|
|
|
|
|
Mr.PoorEnglish wrote: I did so, then started with F5, but VS is too clever for me
Hmmm... well, what should do the trick is:
-compile the CuttingEdge.Conditions project,
-copy the CuttingEdge.Conditions.dll and .xml (but not .pdb) to another directory,
-go to the client project and remove the original reference to the Conditions dll or project,
-Add Reference... and select the copied dll.
-F5!!
Mr.PoorEnglish wrote: can you give me a sample, please?
Of course. Here is one of the simplest methods, IsNotNull:
public static Validator<T> IsNotNull<T>(this Validator<T> validator) where T : class
You can't write it as an instance method, without removing the generic type constraint 'where T: class '. You could of course argue with me about removing the type constraint, but that isn't the issue here. You can look at about every method with a generic type constraint; they can't be rewritten, and in most cases: removing the type constraint won't actually compile. For example look at this method:
public static Validator<TCollection> Contains<TCollection, TElement>(
this Validator<TCollection> validator, TElement element)
where TCollection : IEnumerable<TElement>
{
if (validator.Value == null ||
!Enumerable.Contains(validator.Value, element))
{
Throw.CollectionShouldContain(validator, element);
}
return validator;
}
I hope you see the problem here.
http://www.cuttingedge.it/blogs/steven/
|
|
|
|
|
Ok, I see.
"Contains" would still may fit to my proposed "CollectionValidator<T><t> where T: IEnumerable", but "IsNotNull" would require an additional "ClassValidator", and "IsGreater" would require the "IComparableValidator" and so on....
|
|
|
|
|
When you look at the the ValidatorExtensions.Collection.cs file in the CuttingEdge.Conditions project, you will notice that many extension methods have different type constraints. Each type constraint needs a different validator. For instance, there is the where TCollection : IEnumerable type constraint, and the where TCollection : IEnumerable<TElement> constraint. Same holds for the comparable struff and in the future the number of different type constraints might grow. This would lead to a forest off different ***Validator classes.
But besides all this, there is another problem. You need multiple Require() overload methods that each return a different Validator. This however infects the usability of the API. You would for instance place the IsNotNull method in the ClassValidator and the IsShorterThan methods in the StringValidator and CollectionValidator (and don't forget the GenericCollectionValidator ). But when a CollectionValidator is returned, a user wouldn't be able to access the IsNotNull method. So we then need to implement methods on multiple Validator types. This will lead to double code, double documentation, and users having to browse more methods. Again, we can solve this to write IsNotNull and Evaluate as extension methods, but this lead two a lot of ***Validator classes and extension classes.
http://www.cuttingedge.it/blogs/steven/
|
|
|
|
|
Just a note - the extension methods itself is an syntactic extension of c# 3.0 language. Therefore it's possible to use the extension methods with .net 2.0 framework (although it requires to define one extra attribute in the assembly).
If your library uses only ext. methods (I didn't looked at the source) it can be ported to the .net2.0 easily.
|
|
|
|
|
Thanks for your comment.
I agree with you. The reason that CuttingEdge.Conditions doesn't work with older versions of the .NET framework isn't because of the extension methods. The reason it doesn't work is because it depends on types in System.Core.dll, e.g., System.Linq.Enumerable and System.Linq.Expressions.Expression ).
As a matter of fact, you can add the CuttingEdge.Conditions assembly to your .NET 2.0 project and most of the validation methods work just fine, even if .NET 3.5 isn't installed on the machine. I believe there are currently just two methods in the API that would throw an exception when ran on a machine that hasn't got .NET 3.5 installed on it. One of them could easily be fixed (because it calls Linq.Enumerable.Contains ) and the other uses an Expression<Func<T, bool>> as argument. This last one would be a problem.
But besides all this, I don’t know if it really makes sense to use the library with a language that doesn’t supports extension methods (like C# 2.0 and VB8). Let me give you an example. Here is some simple validation with CuttingEdge.Conditions using C# 3.0:
void MyMethod(IEnumerable<int> collection, object obj)
{
collection.Requires().IsNotNull().IsLessThan(10);
obj.Requires().IsNotNull().IsOfType(typeof(MyObject));
}
</int>
Using C# 2.0, it looks like this:
void MyMethod(IEnumerable<int> collection, object obj)
{
Validator<IEnumerable<int>> collectionValidator =
ValidatorExtensions.Requires(collection);
ValidatorExtensions.IsNotNull(collectionValidator);
ValidatorExtensions.IsLessThan(collectionValidator, 10);
Validator<object> objValidator = ValidatorExtensions.Requires(obj);
ValidatorExtensions.IsNotNull(objValidator);
ValidatorExtensions.IsOfType (objValidator, typeof(MyObject));
}
I doubt if the API is still useful when developers have to write code like this. But if you think otherwise, please let me now. I’m open to all feedback and ideas!
Perhaps there are even other developers who are still working on .NET 2.0 projects and love to use this library. Please let me know! We can discuss the API and if there is enough support for this, I’m willing to change it or even publish a .NET 2.0 compatible version.
http://www.cuttingedge.it/blogs/steven/
|
|
|
|
|
Hi,
well, I agree that it is a question if it makes sense to support that on the compilers without extension methods (but anyway you're doing that already, so you get that for free and it's "their" decision if they want to use it or not).
Personally I can imagine a fluent, c# 2.0 compatible interface (or better semi-fluent). But this would require serious redesign of the whole library and my personal opinion is, that I'd not go that way.
Just as a sample of the syntax I mean, what is the syntactically possible with non-extension method compiler:
Require(collection).IsNotNull().All.IsLessThan(10);
Require(object).IsNotNull().IsOfType(typeof(MyObject));
but the effort required to do this would be greatly higher than to simply use the "correct" compiler with extension methods.
|
|
|
|
|
I just want to make sure you know that you can use C# 3.0 with .NET Framework 2.0. Also, you can use System.Linq.Enumerable in .NET Framework 2.0 with the help of the library called LinqBridge. But I don't think System.Linq.Expressions.Expression is available in .NET Framework 2.0.
|
|
|
|
|
I want to let you know that I (finally) fixed this behavior on the latest code check in on CodePlex. While the library still has a dependency on .NET 3.5, you can now safely ignore that warning when including it in a Visual Studio 2008 .NET 2.0 project.
Note however that the current (beta 3) release does not include these changes. The coming (stable) release will include those bits.
http://www.cuttingedge.it/blogs/steven/
|
|
|
|
|
In your library, you have calls like
id.Requires("id").IsGreaterThan(0);
Where "id" is the name of the parameter. That's kind of redundant.
You can utilize C# 3 expressions to make symbols[^], making your syntax look like id.Requires.IsGreaterThan(0) or even Requires(id).IsGreaterThan(0) .
One last thing regarding the article: your link to the CodePlex project is broken.
|
|
|
|
|
Judah,
Thank you for your reply. I agree with you that entering the name of the parameter is redundant. However, I believe there is no good alternative for this. I like the syntaxes you suggested, but they can’t be expressed in C# 3.0. It’s unfortunately that there is no such thing as 'extension properties', so the id.Requires.IsGreaterThan will not work. To extract the parameter name, we must indeed use expression trees, but the most convenient syntax with C# 3.0 is:
id.Requires(() => id).IsGreaterThan(0) .
And I personally don't like this syntax. I think it's even worse than the original syntax.
But besides disliking the syntax, I find the performance penalty too big, for using expression trees in normal use case scenario's. Such a performance impact would probably prevent many developers from using this library. (If you're interested, you can read more about Condition’s requirements over here).
But to relieve the pain; when you download the CuttingEdge.Conditions runtime library, you’ll find that the .zip file also contains two code snippets. They won’t fix the redundancy in code, but do remove redundancy in typing. Maybe this helps a bit
Thanks again for your comment. I appreciate it very much.
(I fixed the link to the CodePlex project.)
http://www.cuttingedge.it/blogs/steven/
|
|
|
|
|
Hi again.
Thanks for the info.
How about a syntax like Requires(() => id).IsGreaterThan(0)?
Thanks for the article and code, will be giving it a spin this week. By the way, are you aware there are some internal design-by-contract attributes in .NET 3.5? Check out the Microsoft.Contracts.Contract class inside System.Core.dll. Some interesting stuff in there that might influence your designs.
|
|
|
|
|
Hi Judah,
Like I described, I don't really like Requires(() => id) . It just doesn't read as well as Requires("id") and it doesn’t perform well either.
Funny you mention the Microsoft.Contracts namespace.
I am aware of it's existence within the System.Core.dll. I believe that this namespace is primarily used by the Spec# language. As a matter of fact, CuttingEdge.Conditions is inspired by Spec# (you can read that here)!
In my opinion, design by contract (DbC) is the way to go for our software community (and DbC is a much better solution than my library, because it's based on compiler support, which my library lacks, of course). But Spec# is currently a research project and it will probably not be released in the near future. We also shouldn’t expect any validation support like that of Spec# within our mainstream C# language. That's the reason I built this framework. Once we'll have DbC! In the mean time, frameworks like mine can hopefully fill the gap.
http://www.cuttingedge.it/blogs/steven/
|
|
|
|
|
The .NET Junkie wrote: it doesn’t perform well either.
Not to argue with you to death but are you sure about that? One allocates a string, another allocates a delegate. Have you measured to see what this looks like in terms of CPU and memory usage? I'd like to see that before I jump on board in agreement.
The .NET Junkie wrote: In my opinion, design by contract (DbC) is the way to go for our software community
Oh man, I've been asking for this for years now, telling all the Microsoft guys, through their blogs, that Spec# stuff ought to be merged into the C# language at long last.
I'm still holding out hope that C# 4 will get some DbC stuff, but it sounds like it will contain some dynamic and functional features, but nothing with DbC. Who knows. We'll see.
|
|
|
|
|
I'm sorry Judah,
but it's even worse than you would expect. I just did some performance testing. I compared the following two lines:
a.Requires("a").IsNotNull();
compared to:
a.Requires(() => a).IsNotNull();
What's the difference? Well, the first line actually doesn't even allocate a string. It just uses a reference (a 32 bit number) to reference a string that is already allocated (and as long as that string isn't used, there only the cost of copying an Int32 on the stack).
The second isn't a simple delegate allocation. It actually allocates 7 new objects on the heap, makes at least 9 (non-inlinable) method calls and does some heavy reflection to build up the Expression (see for yourself with Reflector). And the worst part is that it doesn't do this only once during initialization. No, it does this every time a call to Requires(() => a) is made.
I found that the second statement is actually over 200 times slower than the first. here
you can take a look at my test code.
I hope you now understand the problems I have with those Expression trees.
About C# 4.0. I sure hope we'll get some DbC stuff in there, but I'm afraid it won't happen. Let's hope I'm wrong.
http://www.cuttingedge.it/blogs/steven/
|
|
|
|
|
The .NET Junkie wrote: I'm sorry Judah,
Don't be sorry, I'm glad you did your homework on this one.
The .NET Junkie wrote: over 200 times slower
That means little. If something takes .00001, 200 times that is still .002. How slow are we talking about in milliseconds?
The .NET Junkie wrote: Well, the first line actually doesn't even allocate a string. It just uses a reference (a 32 bit number) to reference a string that is already allocated (and as long as that string isn't used, there only the cost of copying an Int32 on the stack).
That's not true. That would only be true if the string was already allocated on the heap and string interning is turned on.
All that said, your homework proves you right: using a string is faster and more efficient that building an expression. I'm just wondering how much more efficient we're talking about, as I'm willing to trade a little perf for a nicer programming model.
|
|
|
|
|
I'm sorry, but now you are arguing with me to death
Judah Himango wrote: That means little. If something takes .00001, 200 times that is still .002. How slow are we talking about in milliseconds?
You are actually quite accurate. Of course it depends on the CPU / memory etc. etc., but on my (very fast dev PC) its 0.00002 ms. vs. 0.004 ms. You might find 0.004 ms insignificant, but I actually find this quite significant!
I don't think we will ever agree on whether 0.004 ms. is much or not. But there is actually something else that we haven't discussed yet. We could keep arguing whether or not I should replace the .Requires(string) and .Ensures(string) methods with .Requires(Expression<Func<T>>) and .Ensures(Expression<Func<T>>) . But this actually doesn't have to be. Those string and Expression<Func<T>> overloads could actually quite happily live together and users can than choose the one they like most. I actually might consider this
I think we should move this discussion to another place, because I doubt if the message indenting of this thread can hold much more indents
Judah Himango wrote: That's not true. That would only be true if the string was already allocated on the heap and string interning is turned on.
Technically spoken, you’re right. I wasn’t aware of the possibility to turn off interning (but it’s indeed possible). But besides that; it seems pretty unusual for interning to be off, and interning of this string actually happens during JIT time (when the method that contains the string is ran for the first time). So it's actually a one time cost and therefore I think it’s safe to state that there are no costs.
Are we done fighting now?
http://www.cuttingedge.it/blogs/steven/
|
|
|
|
|
Hahaha, well, it wasn't my intention to argue to death.
So I will end by saying, "thank you" for this excellent library. If my company moves to .NET 3.5, I'd love to introduce it into our codebase.
|
|
|
|
|