I know, I know. Everyone's a gun coder, and nobody ever forgets to check the inputs to their public methods - in the same way as no coder ever makes a mistake, right? Which suggests that all bugs are deliberate...

If you can get away with it, it's worth considering aspect injection for common argument checks, but that's a topic for another day.

Anyway, to the point. Below is a code analysis rule to encourage people to check all parameters to their public methods.

It's not the complete rule; you'll need your own BaseRule class and XML rule definition file, but you can find examples of those elsewhere.

internal class EnforceArgumentChecking : BaseRule
{
    public EnforceArgumentChecking() : base(typeof(EnforceArgumentChecking).Name) { }

    public override Microsoft.FxCop.Sdk.TargetVisibilities TargetVisibility
    {
        get { return TargetVisibilities.All; }
    }

    public override ProblemCollection Check(Member member)
    {
        ProblemCollection problems = new ProblemCollection();

        Method method = member as Method;
        if (ShouldCheck(method))
        {
            Dictionary<string, int> parameterExceptions = GetParameterExceptionCounts(method);

            // require that each parameter have at least one /Argument.*Exception/ associated with it
            foreach (string parameterName in GetParametersToCheck(method))
            {
                // if there's no count for this parameter name, or the count's less than one, we have a problem.
                if ((!parameterExceptions.ContainsKey(parameterName)) || ((parameterExceptions[parameterName] < 1)))
                {
                    Resolution resolution = GetNamedResolution("ArgumentNotChecked", parameterName, method.Name.ToString());
                    problems.Add(new Problem(resolution));
                }
            }
        }

        return problems;
    }

    /// <summary>
    /// Gets a list of the parameter names to check. This will return all parameter names except those
    /// that have a MyCompany.Attributes.SuspressParameterCheck entry for that individual parameter.
    /// </summary>
    /// <param name="method">The method whose parameter collection to scan.</param>
    private IEnumerable<string> GetParametersToCheck(Method method)
    {
        List<string> parametersToIgnore = new List<string>();

        foreach (AttributeNode attr in method.Attributes)
        {
            if (attr.Type.FullName.Equals("MyCompany.Attributes.SuppressParameterCheck"))
            {
                Expression paramName = attr.GetPositionalArgument(0);
                Expression justification = attr.GetPositionalArgument(1);

                if (!string.IsNullOrEmpty(justification.ToString()))
                {
                    parametersToIgnore.Add(paramName.ToString());
                }
            }
        }

        foreach (Parameter p in method.Parameters)
        {
            string paramName = p.Name.ToString();

            if (parametersToIgnore.Contains(paramName))
            {
                continue;
            }

            yield return paramName;
        }
    }

    /// <summary>
    /// Decides whether we should apply this rule to the given method.
    /// </summary>
    /// <param name="method">The method.</param>
    /// <returns>False if the method is null, non-public, an interface or abstract method, a public property setter; true otherwise.</returns>
    private static bool ShouldCheck(Method method)
    {
        if (method == null) { return false; }

        if (!method.IsPublic) { return false; }

        // this will catch methods that are defined as either abstract or interface methods
        if (method.IsAbstract) { return false; }

        if (method.IsAccessor) { return false; }

        // we don't check operators - they all generally call the AreEqual, Add, Append or other named methods anyway - and they're almost
        // always overloads.
        if (method.Name.ToString().StartsWith("op_", StringComparison.Ordinal)) { return false; }

        return true;
    }

    /// <summary>
    /// Looks for the creation of instances of /Argument.*Exception/ and counts them for each parameter on the method.
    /// </summary>
    /// <returns>A dictionary mapping the parameter name to the number of /Argument.*Exception/ thrown against that parameter.</returns>
    private static Dictionary<string, int> GetParameterExceptionCounts(Method method)
    {
        Dictionary<string, int> exceptionsThrownOnParameters = new Dictionary<string, int>();

        // count the exceptions thrown on each parameter.
        for (int i = 2; i < method.Instructions.Count; i++) // start from 2 because it's pretty much impossible to throw a useful exception before this.
        {
            Instruction instruction = method.Instructions[i];

            // are creating a new Argument*.Exception object?
            if (instruction.OpCode == OpCode.Newobj)
            {
                InstanceInitializer initializer = (InstanceInitializer)instruction.Value;
                if (Regex.IsMatch(initializer.FullName, "Argument.*Exception"))
                {
                    for (int paramIdx = 0; paramIdx < initializer.Parameters.Count; paramIdx++)
                    {
                        Parameter p = initializer.Parameters[paramIdx];
                        if (p.Name.ToString().Equals("paramName"))
                        {
                            int paramOffset = initializer.Parameters.Count - paramIdx;

                            Instruction loadStringInstruction = method.Instructions[i - paramOffset];
                            if (loadStringInstruction.OpCode == OpCode.Ldstr)
                            {
                                string parameterValue = loadStringInstruction.Value.ToString();

                                if (!exceptionsThrownOnParameters.ContainsKey(parameterValue))
                                {
                                    exceptionsThrownOnParameters[parameterValue] = 1;
                                }
                                else
                                {
                                    exceptionsThrownOnParameters[parameterValue]++;
                                }
                            }
                        }
                    }
                }
            }
        }

        return exceptionsThrownOnParameters;
    }
}

Hand in hand with this rule is a code attribute that allows the programmer to suppress warnings on individual method parameters, rather than just suppressing the entire rule for a particular method:

[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
public sealed class SuppressParameterCheck : Attribute
{
    private readonly string _parameterName;
    private readonly string _justification;

    public SuppressParameterCheck(string parameterName, string justification)
    {
        if ((string.IsNullOrEmpty(justification)) || (justification.Length < 10))
        {
            throw new ArgumentNullException("justification", "If you're going to suppress a parameter check, you should provide a reason.");
        }

        _parameterName = parameterName;
        _justification = justification;
    }

    public string ParameterName
    {
        get { return _parameterName; }
    }

    public string Justification
    {
        get { return _justification; }
    }
}

And an example of how to use the SuppressParameterCheck attribute:

public void Insert(string key, object value)
{
    if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");
    if (value == null) throw new ArgumentNullException("value");

    // Accept the object, but don't cache it.
}

[SuppressParameterCheck("key", "This will be checked in a called method.")]
[SuppressParameterCheck("value", "This will be checked in a called method.")]
public void Insert(string key, object value, CacheExpireType expireType)
{
    Insert(key, value);    // Accept the object, but don't cache it.
}

Of course, it goes without saying that you're compiling with warnings == errors, right? :)