How to (not) write an especially precarious app on .NET (Compact Framework)

Andrew Arnott

As the .NET Compact Framework developers work to add features, fix bugs, and refactor code, we often have to determine whether a given change could break existing customer code.  The ideal is that NetCF 3.5 will run all apps that ran on NetCF 2.0 and 1.0.  We run hundreds of apps and many, many tests before shipping each product to check backward compatibility.  The .NET Framework (both desktop and CF) makes heavy use of internal classes to allow us the freedom to change the internals of the framework without breaking customer code.  But there are still ways that customers can write apps that may break on future versions.

Compare on exception text

Some exception types are very general and don’t tell you much about the error.  One of these is InvalidOperationException, which can be thrown for a wide variety of reasons for many classes in our BCLs.  Developers usually look at the Exception.Message property to get an idea of what went wrong.  This is by design.  What is not by design is for developers to write code in their apps that looks at the Message property and makes code path decisions based on it.  Here is an example: (highly contrived, admittedly)

    class Program {
        static void Main(string[] args) {
            try {
                XmlSerializer serializer = new XmlSerializer(typeof(MailAddress));
            } catch (InvalidOperationException ex) {
                if (ex.Message == “System.Net.Mail.MailAddress cannot be serialized because it does not have a parameterless constructor.”) {
                    // Print message to user saying he chose a bad type to serialize
                }
            }
            // Success!
        }
    }

This can break in at least two instances:

  1. The text in Exception.Message is localized to the computer running your app, so this code will break if it was run on, say a Spanish computer for example.
  2. A subsequent version of .NET Framework may change the Exception.Message text to be more descriptive, accurate or whatever. 

Either one of these likely cases may cause your code path to take a wrong turn (in this case to assume success).  Instead, you should write code that can analyze exceptions based primarily on exception type and/or error code (enumerable or integer) where available.

Note: It is generally ok to display the Exception.Message to a user of an app in the form of a MessageBox or a log file (there are security considerations in doing this, however) and let the user choose how to proceed.

Compare on Exception type

Another bad way of doing exception handling is to do absolute equality checking on exception types.  Here’s another bad (and contrived) example:

    class Program {
        static void ThrowBadArgument(int positiveValue) {
            if (positiveValue <= 0)
                throw new ArgumentException(“Must be positive”, “positiveValue”);
        }
        static void Main(string[] args) {
            try {
                ThrowBadArgument(-5);
            } catch (Exception ex) {
                if (ex.GetType() == typeof(ArgumentException)) {
                    // Oops, the user provided a non-positive number!
                }
            }
        }
    }

Now suppose that the developer (or vendor) supplying you with your ThrowBadArgument method decided it was more appropriate to throw an ArgumentOutOfRange exception.  This would break your code and again cause your program to inappropriately assume success.

Here is a corrected example:

    class Program {
        static void ThrowBadArgument(int positiveValue) {
            if (positiveValue <= 0)
                throw new ArgumentOutOfRangeException(“Must be positive”, “positiveValue”);
        }
        static void Main(string[] args) {
            try {
                ThrowBadArgument(-5);
            } catch (ArgumentException ex) {
                // Oops, the user provided a non-positive number!
            }
        }
    }

Note in this latter example how the throwing method is using the derived class.  But catching the base class in this way allows us to catch either ArgumentException or ArgumentOutOfRangeException equally well. 

And if you need to check the exception type using an if statement (to check the type of Exception.InnerException for example) be sure to use the is keyword rather than Exception.GetType() == typeof(…).  For example:

// good example
if (ex.InnerException is ArgumentException) {
    // do stuff based on inner exception
}
// Another good example (albeit harder to read)
if (typeof(ArgumentException).IsInstanceOfType(ex.InnerException)) {
}

// BAD example if (ex.InnerException.GetType() == typeof(ArgumentException)) { // do stuff based on inner exception }

Finding public methods using reflection and parameter names

There’s no good way to do this.  You should only find methods based on the parameter types, not parameter names.  The reflection API makes it easy to do it right, and harder to do it wrong.  Here is an example on how to do it right, and wrong:

    class Program {
        static void Main(string[] args) {
            // Good example
            MethodInfo m = typeof(int).GetMethod(“ToString”, new Type[] { typeof(CultureInfo) });
            Console.WriteLine(m.Invoke(3, new object[] { CultureInfo.CurrentCulture }));

// Bad example foreach (MethodInfo method in typeof(int).GetMethods()) { ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length == 1 && parameters[0].Name == “provider”) { m = method; break; } } Console.WriteLine(m.Invoke(3, new object[] { CultureInfo.CurrentCulture })); } }

You should not take a dependency on parameter names of methods you call.  The .NET compilers take care of this for you, but if you go around them by using reflection and use parameter names rather than parameter types to find the method overload you want, you’re asking to be broken if those method parameter names ever change (and they can!)

Finding internal-only methods or types using reflection

Again, there is no right way to do this.  Getting into the internals of a library by using reflection requires full trust and means you’re just asking for your app to break in the next version of the library when those internals get changed.

In conclusion…

If you follow these tips, your apps will be more likely to perform well on current and future versions of the .NET Framework, on your own as well as your customers’ locales.

0 comments

Discussion is closed.

Feedback usabilla icon