简体   繁体   中英

C# Check value inside or outside of properties?

i have written two codes and I am not sure which of the code is better or cleaner. In the first code I check if the first and last name are null in the properties and in the second code I check if the first and last name are null before I even define the properties. I used both methods already but i want to start to write clean code and hopefully someone can tell me what they prefer or what I should use.

First Code:

public class Program
{
    public static void Main(string[] args)
    {
    try
    {
        Person p = new Person(null, null);
    }
    catch(Exception e)
    {
        Console.WriteLine(e.Message);
    }

    Console.ReadKey();
    }
}

public class Person
{
    private string firstName;
    private string lastName;

    public Person(string firstName, string lastName)
    {
        this.FirstName = firstName;
        this.LastName = lastName;
    }

    public string FirstName
    {
        get
        {
            return this.firstName;
        }
        set
        {
            if (firstName == null)
            {
                throw new NullReferenceException("The first name must not be null!");
            }

            this.firstName = value;
        }
    }

    public string LastName
    {
        get
        {
            return this.lastName;
        }
        set
        {
            if (lastName == null)
            {
                throw new NullReferenceException("The last name must not be null!");
            }

            this.lastName = value;
        }
    }
}

or:

public class Program
{
    public static void Main(string[] args)
    {
        string firstName = null;
        string lastName = null;

        try
        {
            if (firstName == null)
            {
                throw new NullReferenceException("The first name must not be null!");
            }
            else if (lastName == null)
            {
                throw new NullReferenceException("The last name must not be null!");
            }
            else
            {
                Person p = new Person(firstName, lastName);
            }
        }
        catch(Exception e)
        {
            Console.WriteLine(e.Message);
        }

        Console.ReadKey();
    }
}

public class Person
{
    public Person(string firstName, string lastName)
    {
        this.FirstName = firstName;
        this.LastName = lastName;
    }

    public string FirstName
    {
        get;
        set;
    }

    public string LastName
    {
        get;
        set;
    }
}

Use first one with a minor change in the Person class.

Or, if you are using MVC model, you can use data annotation.

public class Person
{
    [Required(ErrorMessage = "First Name is required!")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Second Name is required!")]
    public string SecondName { get; set; }
}

Instead of checking firstname and lastname for null, check the passed in value.

Advantages:

  1. Validation logic need not be scattered across classes.

  2. You can create a third property to get full name.

      public class Person { private string firstName; private string lastName; public Person(string firstName, string lastName) { this.FirstName = firstName; this.LastName = lastName; } public string FirstName { get { return this.firstName; } set { if (value == null) { throw new NullReferenceException("The first name must not be null!"); } this.firstName = value; } } public string LastName { get { return this.lastName; } set { if (value == null) { throw new NullReferenceException("The last name must not be null!"); } this.lastName = value; } } 

It depends on why you need validation.

If you use MVVM (which I think you will, since the class is called Person) I would recomend implementing INotifyDataErrorInfo interface and INotifyPropertyChanged (I use ReactiveUI for that). If you you use exceptions for validation in MVVM, there is no way to check if your model bound with UI is valid as a whole, because in fact no invalid value is set to your properties.

This means, that simple part of logic like disabling OK button until all data is valid has to be moved to the UI.

If you also want to serialize Person class, do the checking in derived PersonValidation class, or you will end up with exeptions in many wierd places.

Another whole problem is with properties of type other than string, like int.

I found that the easiest way (I don't claim it to be the best) is to have a string property for each non-string property and bind UI textbox to the string.

     class Person // this is your DTO, for serialization (to JSON and so on) and this goes to database
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public int Age { get; set; }
        }

        class PersonVM : ReactiveObject // this is your object to display in UI
        {
            private string _firstName;
            private string _lastName;
            private int _age;

            public string FirstName
            {
                get { return _firstName; }
                set { this.RaiseAndSetIfChanged(ref _firstName, value); }
            }

            public string LastName
            {
                get { return _lastName; }
                set { this.RaiseAndSetIfChanged(ref _lastName, value); }
            }

            public int Age
            {
                get { return _age; }
                set { this.RaiseAndSetIfChanged(ref _age, value); }
            }
        }

       class PersonVMValidation : PersonVM, INotifyDataErrorInfo // this is your validation class, where you can check if the whole object is valid
{
    public PersonVMValidation()
    {
        this.WhenAnyValue(x => x.AgeString).Subscribe(_ =>
        {
            if (Int32.TryParse(AgeString, out int age))
            {
                if (age >= 0)
                {
                    Age = age;
                    SetError("Age", null);
                }
                else
                {
                    SetError("Age", "Age has to be >= 0");
                }
                //and so on
            }
            else
            {
               // you reset age to 0 for example
               SetError("Age", "Age has to be numeric");
            }
        });

        // do the same for LastName
        this.WhenAnyValue(x => x.FirstName).Subscribe(_ =>
        {
            if (string.IsNullOrEmpty(FirstName))
            {
                SetError("FirstName", "First name cannot be empty");
            }
            else
            {
                SetError("FirstName", null);
            }
        });
    }

    public string AgeString
    {
        get { return _ageString; }
        set { this.RaiseAndSetIfChanged(ref _ageString, value); }
    }

    protected Dictionary<string, List<string>> ErrorsDictionary = new Dictionary<string, List<string>>();
    private bool _hasErrors;
    private string _ageString;


    protected void SetError(string property, string error)
    {
        ErrorsDictionary[property] = new List<string>();
        if (error != null)
        {
            ErrorsDictionary[property].Add(error);
            HasErrors = true;
        }
        else
        {
            if (ErrorsDictionary.All(x => !x.Value.Any()))
                HasErrors = false;
        }

        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Value"));
    }

            var person = someservice.GetPerson();

            PersonVMValidation personValidation = Mapper.Map<PersonVMValidation>(person); // notice no exceptions here, you bind UI to this

            var canSaveCahnges = personValidation.WhenAnyValue(x => x.HasErrors).Select(x => !x);
            SaveChanges = ReactiveCommand.Create(() =>
            {
/*do stuff*/
            }, canSaveCahnges);

Use AutoMapper to create PersonVM form Person and back.

That was for UI apps created with WPF, which has nice built-in tricks to display those errors.

If you use MVC, then go with @Amit 's answer.

If you want it for something else, I think set properties from contructor and check your conditions there (and hide setters) - this would allow you to catch those NullArgumentExceptions with ease.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM