Can I tag a C# function as "this function does not enumerate the IEnumerable parameter"?

StackOverflow https://stackoverflow.com/questions/23546556

  •  18-07-2023
  •  | 
  •  

Вопрос

Multiple enumeration of the same enumerable is something that has been a performance problem for us, so we try to stomp those warnings in the code. But there is a generic extension function that we have for throwing null parameter exceptions that generates a lot of these warnings. Its signature looks like this:

public static void VerifyArgumentIsNotNull<T>(this T value, string valueName) where T : class

All it does is check for null and throw a nicely formatted and localized (for whichever human language is in play at the time) exception.

When this function is used on an IEnumerable parameter, it makes the code analysis warn about a possible multiple iteration of the IEnumerable because the analyzer has no idea what that function does.

I would like to put some tag on this function that says, "Yes, this takes the enumerable as an input, but it does not iterate it and therefore should not be counted as a possible iteration by callers." Is there any such tag? I've searched the Internet to no avail.

Это было полезно?

Решение

Yes, what you're asking is very much possible, but requires a little work. ReSharper uses Code Annotations to add hints to its analysis engine and make more sense of the code it has to work with. I recently recorded a webinar with JetBrains called ReSharper Secrets, where I go into much greater detail about what Annotations are and how to use them. You should watch it!

There's an annotation attribute, [NoEnumeration] that does exactly what you ask - specifies that the given IEnumerable argument is not enumerated, however it's not included in the default Code Annotation Attributes, however it is defined in the JetBrains.Annotations.dll assembly.

So after this introduction, here's what you need to do:

  1. (if you haven't already,) go to ReSharper Options, then Code Inspection → Code Annotations, and press the Copy default implementation to clipboard button
  2. Create a file in any of your (shared) projects called Annotations.cs (or any other name)
  3. Paste the code from the clipboard, completely replacing anything that was previously in Annotations.cs
  4. Add the following definition at the end of the file:

Code:

/// <summary>
/// Indicates that IEnumarable, passed as parameter, is not enumerated.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class NoEnumerationAttribute : Attribute
{
}

After you done this, all that's left to do is place the [NoEnumeration] attribute on the value argument, like this:

public static void VerifyArgumentIsNotNull<T>([NoEnumeration] this T value, string valueName) where T : class
{
    ....
}

And that's it! The warning will disappear!

Bonus:

There are 3 additional attributes you can use to decorate this method to make it even more useful: [NotNull], [ContractAnnotation] and [InvokerParameterName]. I recently describe what they do (and a short demo) in this issue for a similar API called LiteGuard.

Annotations are fun :)

Другие советы

Since VerifyArgumentIsNotNull is generic, but does nothing type specific, it can take an object:

public static void VerifyArgumentIsNotNull(this object @object, string argumentName) { ... }

Resharper (9.11.) assumes that the called method does not cast @object back to an IEnumerable, and thus there is no warning.

Note the lack of the class constraint means the compiler may not warn if you accidentally pass a value type to VerifyArgumentIsNotNull, but Resharper will warn that a value type can never be null.

This approach has the additional advantage of saving the JIT from creating an instance (closed generic) method for every type that VerifyArgumentIsNotNull is called with; a micro-optimization to be sure, but a rare example where a generic may not be preferable to an old-school object.

One possible drawback to the above: I have seen similar implementations where VerifyArgumentIsNotNull returns "value". In this case a return value of type T is needed to avoid an explicit cast. (IMO this syntax is ugly. So it is not a drawback for me.)

Two other editorial comments: 1. A method name I have seen: ThrowIfNull is more concise, and "Throw" is more explicit than "Verify" 2. I no longer use methods for this, because without an annotation on VerifyArgumentIsNotNull Resharper must assume the argument could still be null. I find it much simpler to let R#, insert a 1-line if + throw when I add the NotNullAttribute.

This approach does not work for the broader case where you want to ensure that a method is called with an enumerable. In this case Igal Tabachnik's additions to Annotations are great.

Assuming you are using Visual Studio 2013/2012 (I'm only aware of this feature in 2013 though) in the code analysis window you should be able to right click the message navigate to Suppress Message > In Source File or In Suppression File

Alternatively you can achieve the same effect by clicking the action drop down of the message in the Code Analysis window.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top