A Little Bit Regret for Generic in C#

In our project, I defined the ThrowHelper helper class to help us throw the customized exception. For instance, I defined a exception as below:

public class EmployeeException : ApplicationException

{

    public EmployeeException()

        : base()

    { }

 

    public EmployeeException(string message)

        : base(message)

    { }

 

    public EmployeeException(string message, Exception innerException)

        : base(message, innerException)

    { }

}

 

Then we might define the method to throw it to be invoked by other developer conveniently:

public static class ThrowHelper

{

    public static EmployeeException ThrowEmployeeException(string message)

    {

        LogService.Error(message);

        throw new EmployeeException(message);

    }

 

    public static EmployeeException ThrowEmployeeException(string message, Exception innerException)

    {

        LogService.Error(message, innerException);

        throw new EmployeeException(message, innerException);

    }

}

 

The problem is that we need to add the method such as before if we defined a number of customized exceptions. It’s tedious. The developer must dislike this way whenever.

Can we use the generic type to solve this problem? For example, we may define a new version method using generic type as below:

public static class ThrowHelperGeneric<TCustomException>

    where TCustomException : ApplicationException, new()

{

    public static TCustomException ThrowCustomException(string message)

    {

        LogService.Error(message);

        throw new TCustomException(message);

    }

 

    public static TCustomException ThrowCustomException(string message, Exception innerException)

    {

        LogService.Error(message, innerException);

        throw new TCustomException(message, innerException);

    }

}

 

Opps, it’s regretful. It can’t support to invoke constructor with parameter based on .Net framework. We have to use the reflection technology to solve it:

public static class ThrowHelperGeneric<TCustomException>

    where TCustomException : ApplicationException, new()

{

    public static TCustomException ThrowCustomException(string message)

    {

        LogService.Error(message);

        TCustomException exception = (TCustomException)typeof(TCustomException).GetConstructor(new Type[] { typeof(string) }).

            Invoke(new object[] { message });

 

        throw exception;

    }

 

    public static TCustomException ThrowCustomException(string message, Exception innerException)

    {

        LogService.Error(message, innerException);

        TCustomException exception = (TCustomException)typeof(TCustomException).GetConstructor(new Type[] { typeof(string), typeof(Exception) }).

            Invoke(new object[] { message, innerException });

 

        throw exception;

    }

}

 

It’s a terrible choice. I don’t like to do in this way. Even though we can use new() in the where constraint, how come Microsoft can’t provide the similiar operator or keyword in the where constraint to constrain the type of the parameter in the constructor? Like this:

public static class ThrowHelperGeneric<TCustomException>

    where TCustomException : ApplicationException, new(), new(string), new(string, Exception)

{

    public static TCustomException ThrowCustomException(string message)

    {

        LogService.Error(message);

        throw new TCustomException(message);

    }

 

    public static TCustomException ThrowCustomException(string message, Exception innerException)

    {

        LogService.Error(message, innerException);

        throw new TCustomException(message, innerException);

    }

}

I don’t know whether it will support this function in the furture version with C#. Is it too difficult?

Programming Standard for Our Team

1. Exception Handling
1) Deprive all custom exceptions from System.ApplicationException, and putting all custom exceptions into WD.AIATools.Common.Exceptions namespace;
2) Define three overloaded version for constructor, such as:

public class ActionParamsException : ApplicationException

{

    public ActionParamsException()

        : base()

    {    }

 

    public ActionParamsException(string message)

        : base(message)

    {    }

 

    public ActionParamsException(string message, Exception ex)

        : base(message, ex)

    {    }

}

3) Provide a helper method which includes the logging operation for your custom exception in ThrowHelper class:

    public static ActionParamsException ThrowActionParamsException(string message, Exception ex)

    {

        LogService.Error(message, ex);

        return new ActionParamsException(message, ex);

    }

4) In Data Access Layer, try to catch the exception which is thrown by .NET Framework, and re-throw the custom exception by invoking the helper method with ThrowHelper. For example:

        try

        {

            Foo();

        }

        catch (SqlException ex)

        {

            throw; //Avoid

            ThrowHelper.ThrowCustomException(“message”, ex); //OK

        }

        catch (NotSupportedException ex)

        {

            ThrowHelper.ThrowCustomException(“message”, ex); //OK

        }

        catch (Exception ex)

        {

            ThrowHelper.ThrowCustomException(“message”, ex); //OK

        }

        finally //if necessary

        {

            //do something

        }

5) Don’t catch the exception in Business Logic Layer otherwise it is necessary.
6) In Presentation Layer, it includes two cases:
a) Show the error information on the current page through by catching the exception;
b) Link to the error page which includes the error information with Exception.Message;
7) Don’t catch the exception inside for or foreach statement.

 

2. Robust Check Policy
1) Avoid throwing the NullReferenceException. Check whether the object is null before using it:

if (someObject != null)

{

    //Do something;

}

else //if necessary

{

    //throw the custom exception or else

}

2) Never return the null value especially if the object is collection:

public IList GetSomeList()

{

    //Something to do;

    if (NoItemInCollection)

    {

        return new List(); //OK

        return null; //Avoid;

    }

}

3) Always explicitly initialize an array of reference types using a for loop:

public class MyClass{}

 

const int ArraySize = 10;

MyClass[] array = new MyClass[ArraySize];

for (int index = 0; index < ArraySize; ++index)

{

    Array[index] = new MyClass();

}

4) Prefer to use foreach statement instead of for statement;
5) Try to check what the instance’s type is before casting the reference type by using “is” operator:

if (someObject is Foo)

{

    Foo object = (Foo)someObject;

}

//Or

Foo object = someObject as Foo;

6) Remember to dispose the resource when using the unmanaged resource; Always use a using statement in this situation.

3. Code Practices
1) Avoid putting multiple classes in a single file;
2) Never hard-code a numeric value; always declare a constant instead;
3) Avoid providing explicit values for enums unless they are integer powers of 2:

//Correct

public enum Color

{

    Red, Green, Blue

}

Note: Putting all enum types into the specific file.
4) Use String.Empty instead of “”;
5) When building a long string, use StringBuilder, not String;
6) Always have a default case in a switch statement;