Click here to Skip to main content
12,501,833 members (52,378 online)
Click here to Skip to main content
Add your own
alternative version

Stats

88.6K views
934 downloads
35 bookmarked
Posted

I take exception to that argument!

, 30 Dec 2004 MIT
Rate this:
Please Sign up or sign in to vote.
Achieve greater consistency in raising argument exceptions with very little effort. This utility class (which is covered by nearly 600 included NUnit test cases!) makes it possible to evaluate and raise exceptions on passed-in parameters, using a single line of code.

Introduction

Admit it: you’re probably more lax than you should be about parameters that get passed into the class members you write. Laziness strikes every developer from time to time. I myself got tired of typing the following code every other minute:

if (value == null)
    throw new ArgumentNullException("value");

I got lazy enough that I decided to make testing member arguments and raising exceptions on them as effortless as possible. The result of my laziness is a utility class that makes it easy to be strict about what you allow callers to pass into your code.

Since this code is likely to be of use in every part of most of your C# projects, I also went to the trouble of writing nearly 600 NUnit test cases that you can run to verify my code’s correctness and to get a feel of how to call this utility class from your code. (See Testing the code below, for information on how to run the test cases.)

Using the code in your projects

This utility class is designed to be included in your C# projects, not referenced as a compiled assembly. The reasons for this should be obvious, but you definitely don’t want dozens of copies of a C# source file all over your source tree. So put the file in the root of your source code directory and make a link (see below) to that one file in each of your C# projects.

linking to a single copy of Exceptions.cs in Visual Studio

Exceptions.cs has nine static methods (most of them heavily overloaded) that test for argument constraint violations and throw the appropriate standard argument exception. The methods are thoroughly documented in XML documentation comments within the source and in an HTML Help file generated from those comments. Examples are included in the documentation to show how the methods can be used most effectively in your code—everything from implementing from .NET Framework Class Library Reference at MSDN Library" href="http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemicomparableclasstopic.asp" target="_blank">IComparable, to extending Reference at MSDN Library" href="http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemcollectionscollectionbaseclasstopic.asp" target="_blank">CollectionBase, to argument validation in constructors, methods and properties. Briefly summarized, the methods are:

Why not simply Assert?

It’s good defensive programming practice to sprinkle Debug.Assert() liberally throughout your code in order to exterminate bugs before they crawl around and lay eggs. You might be wondering when you should opt to throw an exception instead of asserting a condition.

Here’s a rule of thumb:

  • Within private and internal members, call Debug.Assert().
  • Within protected, protected internal and public members, call Exceptions.ThrowIf… and/or throw custom exceptions.

My rationale is that code running in a different assembly should be given the opportunity to catch properly formatted exceptions. Assertions are for internal use only—housekeeping notes to yourself. Keep in mind that they don’t even get evaluated in release builds.

Testing the code

There’s no reason why you should trust that this code will do what it’s supposed to do. In fact, there’s little reason, apart from rigorous testing, why you should trust any source code in your projects, whether you wrote it or somebody else did. If the word “testing” sounds to you like somebody else’s job, then you’re passing the buck. And you’re also missing out on a way both to drastically improve the quality of your code and to reduce your stress about the quality of your code. Sounds impossible? Read about Alphonse (read the rest of the series, too) for an entertaining introduction to test-driven development. The XP gurus have informative things to say about TDD, too.

Whether you’re new to automated unit testing or are an experienced user of NUnit, MbUnit or other unit-testing frameworks, you should download and install a wonderful Visual Studio .NET add-in called TestDriven.NET. It provides a “friction-free” developer experience for running unit test cases from within the Visual Studio .NET 2003 IDE. You don’t need the other frameworks installed first; it comes complete. In fact, you might want to uninstall NUnit and MbUnit before installing TestDriven.NET.

Seriously: download it and install it right now. I’ll wait.

Back already? With TestDriven.NET installed, open the solution, Umbrae.Exceptions.Test.sln, in Visual Studio .NET 2003. Then right-click on ExceptionsTest.cs in the IDE and click the Run Test(s) menu item.

running automated unit test cases in Visual Studio .NET 2003 using TestDriven.NET

You should be able to see all the tests execute successfully in the IDE’s Output window:

------ Test started: Assembly: Umbrae.Exceptions.Test.dll ------


558 succeeded, 0 failed, 0 skipped, took 8.98 seconds.



---------------------- Done ----------------------

Feedback

I welcome your input on this, whether it be bug reports, flames, enhancement ideas, or unit tests I’ve neglected to write.

History

Wed 12/29/2004: Updated

  • Applied DebuggerHidden attribute to all Exceptions members that throw exceptions.
  • Updated HTML Help documentation to reflect member attributes.

Wed 12/22/2004: Updated

  • Changed out some thrown exceptions for Debug.Assert() calls in Exceptions.cs.
  • Updated XML documentation to cite fewer exceptions that may be thrown.
  • Updated examples to take into account assertions that may fail due to improper calls.
  • Commented out 66 unit tests that fail the new assertions.
  • Fixed a bug in ExceptionsTest.TemporaryTypeBuilder.CreateEnumTypeWithFlagsAttribute().
  • Shortened the method names.
  • Added four missing unit tests for ThrowIfDifferentRank().
  • Changed ThrowIfNull() to avoid unnecessarily composing the Message property.
  • Corrected typographical errors in the XML documentation.
  • Added remarks to the article on calling Debug.Assert() vs. throwing exceptions.
  • Made various changes to the text of the article.

Fri 12/10/2004: Originally submitted

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Nils Jonsson
Architect NCite
United States United States
I’m part of a startup based in Houston, building products for law enforcement. I speak Ruby (since 2005), JavaScript (since 2005), and C# (since 2002).

You may also be interested in...

Pro
Pro

Comments and Discussions

 
GeneralFxCop warnings Pin
Jaap Faber3-Jul-07 1:49
memberJaap Faber3-Jul-07 1:49 
GeneralNice, but... Pin
Sander Leer28-Dec-04 2:56
sussSander Leer28-Dec-04 2:56 
GeneralRe: Nice, but... Pin
Nils Jonsson28-Dec-04 9:57
memberNils Jonsson28-Dec-04 9:57 

I get your point but I don’t see how it makes much practical difference. To illustrate, if a class library uses Exceptions.cs to raise an argument exception, here’s what I see in my Call Stack window when an exception is thrown to my WinForms client:


  • Umbrae.Class­Library1.dll!Umbrae.Exceptions.Throw­If­Out­Of­Range­Include­Max­Core(System.IComparable value = {0}, string name = "positive­Argument", System.Value­Type lower­Bound = {0}, System.Value­Type max­Value = {2147483647}) Line 2922
  • Umbrae.Class­Library1.dll!Umbrae.Exceptions.Throw­If­Out­Of­Range­Include­Max(int value = 0, string name = "positive­Argument", int lower­Bound = 0, int max­Value = 2147483647) Line 2373
  • Umbrae.Class­Library1.dll!Umbrae.Class­Library1.Class1.My­Method(int positive­Argument = 0) Line 21
  • Umbrae.Windows­Application1.exe!Umbrae.Windows­Application1.Form1.button1_Click(System.Object sender = {Text="button1"}, System.Event­Args e = {System.Event­Args}) Line 90
  • [<Non-user Code>]
  • Umbrae.Windows­Application1.exe!Umbrae.Windows­Application1.Form1.Main() Line 84

I don’t know what’s confusing or bothersome about that. Exceptions.Throw­If­Out­Of­Range­Include­Max­Core() and Exceptions.Throw­If­Out­Of­Range­Include­Max() seem to be self-explanatory as internal methods for checking argument validity. And if Umbrae.Class­Library.dll is referenced as a compiled assembly without source code, it won’t show up in the Call Stack anyway unless the developer chooses the “Show Non-user Code” preference.


Besides, this kind of somewhat extraneous information already shows up in the stack trace if you pass a bad argument, say, to System.Windows.Forms.Check­Box.OnPaint(): a null argument throws a Null­Reference­Exception with a stack trace that starts out:


  • System.Windows.Forms.dll!System.Windows.Forms.Check­Box.Paint­Up(System.Windows.Forms.Paint­Event­Args e = <undefined value>, System.Windows.Forms.Check­State state = Unchecked) + 0x51 bytes
  • System.Windows.Forms.dll!System.Windows.Forms.Check­Box.Paint­Over(System.Windows.Forms.Paint­Event­Args e = <undefined value>, System.Windows.Forms.Check­State state = Unchecked) + 0x48 bytes
  • System.Windows.Forms.dll!System.Windows.Forms.Check­Box.Paint­Standard(System.Windows.Forms.Paint­Event­Args pevent = <undefined value> ) + 0x33 bytes
  • System.Windows.Forms.dll!System.Windows.Forms.Check­Box.Paint­Control(System.Windows.Forms.Paint­Event­Args pevent = <undefined value> ) + 0x32 bytes
  • System.Windows.Forms.dll!System.Windows.Forms.Check­Box.OnPaint(System.Windows.Forms.Paint­Event­Args pevent = <undefined value> ) + 0x35 bytes
  • Umbrae.Windows­Application1.exe!Umbrae.Windows­Application1.My­Check­Box.On­Click(System.Event­Args e = {System.Event­Args}) Line 21
  • [...]
  • Umbrae.Windows­Application1.exe!Umbrae.Windows­Application1.Form1.Main() Line 94

Note that I had to choose the “Show Non-user Code” preference to get the WinForms methods to show up in the Call Stack.


I say stop worrying about confusing developers in this way.



-- /\/. _/.
GeneralRe: Nice, but... Pin
Sander Leer29-Dec-04 0:19
memberSander Leer29-Dec-04 0:19 
GeneralRe: Nice, but... Pin
Nils Jonsson29-Dec-04 4:26
memberNils Jonsson29-Dec-04 4:26 
GeneralRe: Nice, but... Pin
ip25528-Dec-04 10:48
memberip25528-Dec-04 10:48 
GeneralRe: Nice, but... Pin
Nils Jonsson29-Dec-04 4:55
memberNils Jonsson29-Dec-04 4:55 
GeneralSpeed/size analysis Pin
Ed Brey22-Dec-04 3:58
memberEd Brey22-Dec-04 3:58 
GeneralRe: Speed/size analysis Pin
Nils Jonsson22-Dec-04 5:36
memberNils Jonsson22-Dec-04 5:36 
GeneralRe: Speed/size analysis Pin
Ed Brey22-Dec-04 5:51
memberEd Brey22-Dec-04 5:51 
GeneralRe: Speed/size analysis Pin
Nils Jonsson22-Dec-04 6:54
memberNils Jonsson22-Dec-04 6:54 
GeneralRe: Speed/size analysis Pin
peterchen23-Dec-04 3:12
memberpeterchen23-Dec-04 3:12 
QuestionWhy not assert? Pin
Marc Clifton20-Dec-04 11:24
protectorMarc Clifton20-Dec-04 11:24 
AnswerRe: Why not assert? Pin
Nils Jonsson21-Dec-04 11:13
memberNils Jonsson21-Dec-04 11:13 
GeneralNice work. Pin
Angelos Petropoulos20-Dec-04 0:51
memberAngelos Petropoulos20-Dec-04 0:51 
GeneralRe: Nice work. Pin
Nils Jonsson20-Dec-04 5:42
memberNils Jonsson20-Dec-04 5:42 
GeneralRe: Nice work. Pin
Angelos Petropoulos20-Dec-04 5:52
memberAngelos Petropoulos20-Dec-04 5:52 
GeneralLong names Pin
Frank Hileman15-Dec-04 17:16
memberFrank Hileman15-Dec-04 17:16 
GeneralRe: Long names Pin
Nils Jonsson16-Dec-04 10:55
memberNils Jonsson16-Dec-04 10:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160919.1 | Last Updated 31 Dec 2004
Article Copyright 2004 by Nils Jonsson
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid