3
\$\begingroup\$

What do you think about the following syntax for some very simple and intensively reusable validations?

Does this:

string name = (SomeText)"Hm…";

mean for you a none-safe cast to none-empty required text? How does it play with C# syntax/semantics? So these two should throw:

string name = (SomeText)null;
string name = (SomeText)"";

More examples:

int Age = (AdultAge)43; // OK
int Age = (AdultAge)10; // throws…

Basically, I am just looking for type alias with a guard constrain, but not so many things are possible in C#...

Let’s define the casts (yes, it is ugly):

class SomeText : Constrain<SomeText, string>
{
    public static implicit operator SomeText(string value) =>
        Create(value, !string.IsNullOrWhiteSpace(value));
}

class AdultAge : Constrain<AdultAge, int>
{
    public static implicit operator AdultAge(int value) =>
        Create(value, value >= 21);
}

Where library code is:

abstract class Constrain<TConstrain, TValue>
    where TConstrain : Constrain<TConstrain, TValue>, new()
{
    public static implicit operator TValue(Constrain<TConstrain, TValue> check) => 
        check.Value;

    protected static TConstrain Create(TValue value, bool satisfied, string message = null)
    {
        if (!satisfied)
            throw new ArgumentException(message ?? $"{typeof(TConstrain).Name} required.");
        else
            return new TConstrain() { Value = value };
    }

    TValue Value { get; set; }
}

P.S. More examples:

string email = (EMail)"test@example.com";
Stream stream = (InputStream)File.OpenText("c:\Autoexec.bat");
double weight = (Positive)160;

P.P.S. Even more examples:

class Adult
{
    public Adult(string name, int age)
    {
        Name = (SomeText)name;
        Age = (AdultAge)age; 
    }

    public override string ToString() => $"{Name}, {Age}";
    string Name { get; }
    int Age { get; } 
}
\$\endgroup\$
  • 1
    \$\begingroup\$ I don't see why you'd cast to a special type and then immediately cast back again, instead of simply using methods like int validateIsAdultAge(int age). \$\endgroup\$ – Tanner Swett Jun 14 '16 at 1:24
  • 1
    \$\begingroup\$ If you want strict typing, then how come you're immediately casting SomeText and AdultAge back to string and int instead of leaving them as SomeText and AdultAge? \$\endgroup\$ – Tanner Swett Jun 14 '16 at 2:03
  • 1
    \$\begingroup\$ I would call it arabic programming because if you wanted to apply multiple validations you would have to do it from right to left ;-) it's strange, interesting, weird and somehow genius at the same time. \$\endgroup\$ – t3chb0t Jun 14 '16 at 8:38
  • 1
    \$\begingroup\$ This seems to be very limited too. You cannot validate for a range or a specific value unless you write a specialized validator for is-between 2-4... or can you? \$\endgroup\$ – t3chb0t Jun 14 '16 at 8:42
  • 1
    \$\begingroup\$ @t3chb0t Yep, we need to subclass. It is just a shortcut for something like NotNull, no more than that... \$\endgroup\$ – Dmitry Nogin Jun 14 '16 at 8:44
2
\$\begingroup\$

Your recent questions give me impression, that you are purposely trying to to find new ways to mock C# syntax. :) You know this saying from MSDN: "code is read way more often, than it is written"? I think you have to keep this in mind, when designing those things. I would definitely have a WTF-moment, if I were to see int Age = (AdultAge)43 in code. I then would have to go and look for AdultAge and Constrain implementations in order to figure out what is going on there. I would rather have this:

Validator.ValidateEmail("test@example.com");

or this:

reference.ThrowIfNull();

or even this:

var age = (Age)21;
if (age.IsAdult) ...

Because those options are easy to read and easy to understand. They feel natural. Your code does not. It's clever, but it is obscure and it is easy to get wrong:

int Age1 = (AdultAge)43; // OK
var Age2 = (AdultAge)43; // OK?
double Age3 = (AdultAge)43.1; // OK?
var Age4 = String.Format("Age: {0}", (AdultAge)43); // OK?
\$\endgroup\$
  • \$\begingroup\$ Yep, agree. Not so much can be done here without proper support of the language. Defining new type just to have a ctor with some restriction applied is a way to expensive. "SmartPointers" do not work in C#... \$\endgroup\$ – Dmitry Nogin Jun 14 '16 at 12:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.