Verifying Critical Parameters






Verifying Critical Parameters

Problem

You have a method, property, or indexer that requires the correct value or set of values to be passed in to it (e.g., cannot be null, must be within a numeric range or a set of numeric ranges, the enumeration value must be a valid value in the enumeration). If an incorrect value is passed in to the method, it must inform the application and handle the invalid value gracefully.

Solution

The parameters passed in to a public method should always be tested for correctness before they are used; however, it may be more appropriate to use Debug.Assert or even to use no tests when checking parameters to nonpublic methods. If one or more fail the test, an ArgumentException, or one of its derivatives, should be thrown to ensure that the application is notified that critical data has possibly been corrupted. (Note that an IndexOutOfRangeException could instead be thrown from within an indexer.)

When a numeric parameter that is out of a specified range is passed, the ArgumentOutOfRangeException should be thrown. The following code checks whether the numberOfItems parameter is greater than an upper bound of 100:

	public static void TestParams(int numberOfItems, object myObject, Language language)
	{
	    Debug.Assert(numberOfItems <= 100);
	    if (numberOfItems > 100)
	    {
	        throw (new ArgumentOutOfRangeException("numberOfItems", numberOfItems,
	              "The number of items has exceeded the defined limits."));
	    }
	    
	    //…trailing code
	}

Many parameters passed to methods may produce strange results when they are null. To prevent this from happening, test the parameters. If any of them is null, throw the ArgumentNullException.

The following code checks the myObject object variable to see whether it is null:

	public static void TestParams(int numberOfItems, object myObject, Language language)
	{
	    //… previous code
	    
	    Debug.Assert(myObject != null);
	    if (myObject == null
	    {
	        throw (new ArgumentNullException("myObject",
	                "The object passed may not be null."));
	    }
	    
	    //…trailing code
	}

If a method accepts an enumeration value, a caller may pass a numeric value in lieu of an enumeration value of the parameter's type. This is dangerous since the caller can easily pass in a number that does not exist in the enumeration. To prevent this problem, test for the specific enumeration values that are allowed for the enumeration-type parameter using a switch statement to list the values. There is a static IsDefined method on the Enum class, which you should avoid. IsDefined uses reflection internally, thereby incurring a performance penalty, and does not handle versioning of the enumeration well. Consider if the value MgdCpp (for managed C++) is added to the Languages enum in the next version of your software. If you use IsDefined to check the argument here, it will allow MgdCpp as a valid value as it is defined in the enumeration, even though the code for which you are validating the parameter is not designed to handle it. By being specific with the switch statement, the MgdCpp value will be rejected, and the code will not try to run in an invalid context. If the parameter contains a bad value (falls through to the default case in the switch), throw the InvalidEnumArgumentException. The following code shows how to test for a bad enumeration value:

	public static void TestParams(int numberOfItems, object myObject, Language language)
	{
	    //… previous code
	    
	    switch (language)
	    {
	        // All valid types for the enum listed here.
	        // This means only the ones we specify are valid
	        // not any enum value for this enum.
	        // NOTE: All and VB6 enum types are not valid for this method….
	        case Language.CSharp:
	        case Language.Other:
	        case Language.VBNET:
	            break;
	        default:
	            Debug.Assert(false, language +
	                    " is not a valid enumeration value to pass.");
	            throw (new
	                    System.ComponentModel.InvalidEnumArgumentException("language",
	                        (int)language, typeof(Language)));
	            break;
	    }
	    
	        //…trailing code
	}

Discussion

Testing parameters in this way does not have to be done on every method. Instead, you should test the parameters that are passed in to all public methods of public classes and throw an exception only if they are in error. For nonpublic methods, you can add Debug.Assert statements to test these parameters.

Being in control of the code within your assembly makes it much easier for you to know which valid parameters, their ranges, and so on, you need to pass. Someone who is unfamiliar with your assembly has a much higher chance of passing in bad arguments to the parameters in the assembly's public interface. Therefore, you should guard against other developers passing bad parameters to methods in your assembly by having both a Debug.Assert statement checking the condition and code to handle the problem if the condition is not met at runtime. The Assert will help developers figure out what is going wrong faster, and the handling code will protect you when running.

The more general exceptions, such as ArgumentException, were designed this way, so that the more specific exceptions, such as ArgumentNullException, can be wrapped with the more general exceptions, such as ArgumentException. This specificity gives a much clearer picture of how and where the exception occurred.

See Also

See the "ArgumentException Class" topic in the MSDN documentation.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows