2

I have the following function:

public void Test(string testString)
{
     //Do Stuff
}

At some points in my code, I have to repeatedly check if the parameter is empty string/null/whitespace to skip the body method. The usual ways I've done this till now, are the following:

public void Test(string testString)
{
     if(!string.IsNullOrWhiteSpace(testString))
     {
         //Do Stuff only if string has text in it.
     }
}

Or

public void Test(string testString)
{
     if(string.IsNullOrWhiteSpace(testString)) { return; }
     //Do Stuff only if string has text in it.
}

Is there a way to create a custom attribute that checks if the parameter of the function is empty etc, to skip the method? I've had some experiece (basic stuff), with custom attributes, but I can't figure out a way to make the attribute skip the method body.

The ideal end product of the implementation would be the following:

[SkipIfEmptyParameter]
public void Test(string testString)
{
     //Do Stuff only if string has text in it.
}

Of course, any suggestion is welcome that helps minimize the recurring code if the attribute implementation is not possible.

Edit: Example of the problem I want to solve.

I have the following methods. I get from Microsoft Test Manager, some parameters that our test scenario are expecting (what the values should be). There is a SharedStep implementation that asserts the user's info:

public void AssertUser(UserDTO expectedUserInfo)
{
    VerifyUserName(expectedUserInfo.name);
    VerifyUserSurname(expectedUserInfo.surname);
    VerifyUserAge(expectedUserInfo.age);
    VerifyUserHeight(expectedUserInfo.height);
}

private void VerifyUserName(string name)
{
     //If the string parameter is empty, means the MTM scenario does not
     //want to validate the user's name at this point, so skip the
     //verification below.
     if(string.IsNullOrWhiteSpace(testString)) { return; }

     //Do Stuff only if string has text in it.
}

private void VerifyUserSurname(string surname)
{
     //If the string parameter is empty, means the MTM scenario does not
     //want to validate the user's surname at this point, so skip the
     //verification below.
     if(string.IsNullOrWhiteSpace(testString)) { return; }
     //Do Stuff only if string has text in it.
}

private void VerifyUserAge(string age)
{
     //If the string parameter is empty, means the MTM scenario does not
     //want to validate the user's age at this point, so skip the
     //verification below.
     if(string.IsNullOrWhiteSpace(testString)) { return; }
     //Do Stuff only if string has text in it.
}

private void VerifyUserHeight(string height)
{
     //If the string parameter is empty, means the MTM scenario does not
     //want to validate the user's height at this point, so skip the
     //verification below.
     if(string.IsNullOrWhiteSpace(testString)) { return; }
     //Do Stuff only if string has text in it.
}

The "Do Stuff" contain Selenium implementation that handle WebElements and might be time consuming, so if we don't want to validate that specific value, we just skip the whole method.

Now, when creating the scenarios over to Microsoft Test Manager, the shared steps allows the tester to decide what elements of the page will be validated. If some of the parameters are empty, then the code just skips the blocks and goes to w/e validation the user wants (still, the implementation is for every info the user has, but we just assign value to each parameter we want to test, and every parameter that does not have a value, just gets it's method body skipped).

The problem is, if I want to change the condition of skipping the method, I will have to go to each method and manually change the IF statement. Hence why I though it might be a good idea to have an attribute for every method that validates information.

P.S. I'm talking about hundreds of methods that have the IF implementation at the start.

  • 1
    Is it really worth it? Checking parameter and return is not much code and clearly expresses the intent. – Evk Apr 13 '18 at 12:29
  • Who does call this methods and how ? – Evgeny Gorbovoy Apr 13 '18 at 12:30
  • 1
    This smells like an XY problem. What is the problem your code tries to solve? – Sefe Apr 13 '18 at 12:33
  • There is no proper decision for such task in c#, because it is senseless to call method knowing it won't execute. If your methods are part of API for example, you can implement this logic in invoking layer, so that's why i'm asking who call this methods. in C# you can use Code contracts for validation https://learn.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts – Evgeny Gorbovoy Apr 13 '18 at 12:34
  • @Sefe There is no actual "problem", as of the definition of a problem, the code as is works fine, I'm trying to see if there is a way to have every condition in one place to globally change it if required. The conditions stated above are for MSTests that use Microsoft test manager to get parameters, but recently, our team decided that instead of empty string, if the string is also "<>", we have to skip the method body too. So i'm searching if there is a way to have the IF in a single position, so in the future, if needed, we just change that line of code. Instead of 100+ IF statements. – Thodoris Koskinopoulos Apr 13 '18 at 12:36
  • There is always a problem you are trying to solve. Repeating the same check multiple times is a code smell. Did you use the right design? Can you refactor the code to avoid those checks? Who knows, unlesss you provide some context. – Sefe Apr 13 '18 at 12:41
  • Updated the question with some extra information – Thodoris Koskinopoulos Apr 13 '18 at 12:55
  • Why not encapsulate all logic in a static method? `if (Check.ShouldSkipValidation(username)) return` – Evk Apr 25 '18 at 11:44

4 Answers4

2

The only way that I know that this can be done using attributes is aspect oriented programming using a product like post sharp and method interception. Alternatively if the methods are defined in an interface this can also be done by using RealProxy but seems more than a little overkill.

fanuc_bob
  • 857
  • 1
  • 7
  • 20
0

I don't think attributes make it possible to achieve what you are trying to achieve.

But you can use a custom method invoker instead:

static void Main(string[] args)
{
    InvokeIfNotNullOrWhitespace((inputStr) => TestMethod(inputStr), null);
    InvokeIfNotNullOrWhitespace((inputStr) => TestMethod(inputStr), "");
    InvokeIfNotNullOrWhitespace((inputStr) => TestMethod(inputStr), "abc");

    // RESULT:
    // Trying to invoke action...
    // Trying to invoke action...
    // Trying to invoke action...
    // I have been invoked!
}

static void InvokeIfNotNullOrWhitespace(Action<string> action, string inputString)
{
    Console.WriteLine("Trying to invoke action...");
    if(!string.IsNullOrWhiteSpace(inputString))
        action.DynamicInvoke(inputString);
}

static void TestMethod(string input)
{
    Console.WriteLine("I have been invoked!");
}

The reason why I think attributes won't work is because they can't control what is going on inside the method. Instead, "other external things" can look at those attributes and decide what to do.

To achieve what you are trying to achieve, an "external thing" would need to look at the attribute and decide if it is executed or not. This would be equivalent to what I wrote: an external invoker that unifies the "check string validity" procedure.

Xavier Peña
  • 7,399
  • 9
  • 57
  • 99
  • that is basically calling another method which will check validation of parameter and decide whether to call final method or not! I guess OP must be aware of this approach. – Amit Apr 25 '18 at 10:29
0

Here are my 4 cents on this,

  1. Calling an attribute involves reflection, already a bad idea as you need to find out if the attribute is set;
  2. You're avoiding a "1 liner" in your code that actually is quite easy to type;
  3. Use method overloading;
  4. You can use Aspect oriented programming that will basically inject the below samples in your code at compile time. You can control the way this works with annotations and would not have a negative effect on the generated runtime.

Here are some variations:

//1
if(string.IsNullOrEmpty(testString))
   return;
//2
if(string.IsNullOrEmpty(testString) ||string.IsNullOrWhiteSpace(testString) )
   return;

When going for 3 just make sure you do not mix returning null, or boolean true/false based on the "missing" text. Only you know how your code should flow.

Perhaps you are looking for method overloading you can do that by creating 2 methods with the same name in the same class. You can call the empty MyMethod() from the MyMethod(with string) so you do not duplicate the logic.

return string.IsNullOrEmpty(testString)?MyMethod():MyMethod(testString);
halfer
  • 19,824
  • 17
  • 99
  • 186
Walter Verhoeven
  • 3,867
  • 27
  • 36
0

The way you are doing it is actually pretty good. But as Evk pointed out in the comments: You should extract the "skip checking" into a separate method, especially if the check is always the same and needs to be changed globally. Using an attribute would solve the problem, but is a little complicated to use.

Instead, take a look at the code below. Looks pretty clear, doesn't it? Don't use too many comments (and don't copy-paste them into every method, that is of no use). This way, you have the same benefits as if you would use a custom attribute but without the ugliness of using reflection.

public void AssertUser(UserDTO expectedUserInfo)
{
    VerifyUserName(expectedUserInfo.name);
    VerifyUserSurname(expectedUserInfo.surname);
    VerifyUserAge(expectedUserInfo.age);
    VerifyUserHeight(expectedUserInfo.height);
}

private void VerifyUserName(string name)
{
    if (ShouldSkipValidation(name)) return;
    // code here...
}

private void VerifyUserSurname(string surname)
{
    if (ShouldSkipValidation(surname)) return;
    // code here...
}

private void VerifyUserAge(string age)
{
    if (ShouldSkipValidation(age)) return;
    // code here...
}

private void VerifyUserHeight(string height)
{
    if (ShouldSkipValidation(height)) return;
    // code here...
}

// The MTM scenario does not want to validate values that satisfy the check below
private bool ShouldSkipValidation(string value)
{
    return string.IsNullOrWhiteSpace(value) || value == "<>";
}
Manuel Allenspach
  • 12,467
  • 14
  • 54
  • 76