Search code examples
c#objectmethods

Update object method, with optional parameters


I have a class named car, with properties color, and year. I'm trying to create an update method, so that it updates only the parameters provided, which can be color, year or both, but if no parameters are given, the car object remains with the old properties.

Here's what I've done

internal class car
{
    public car()
    {

    }

    public car Update(string arg_color = null, int? arg_year = null)
    {
        //Problem is here, I want it to keep the old color when I use SomeOldCar.Update(*DifferentYear*)

        car NewCar = new car();
        NewCar.color = arg_color;

        NewCar.year = arg_year;

        return car;
    }

    public string? color { get; set; }
    public int? year { get; set; }
}

Solution

  • Before answering your question - which I'll do at the end - there are a few different concepts you've mixed together that would be helpful to separate out and understand first.

    1. How to update properties on an object

    Using its Properties directly

    Your color and year properties have both get and set methods. This means anyone can update them, without even needing your Update method, just like this:

    Car myCar = new Car();
    
    myCar.Color = "Red";
    

    So technically you've already achieved your objective without even needing your Update method; albeit maybe accidentally.

    Using an Update method

    It's perfectly valid to have an Update method like you've done. This is often used when there is significant validation (i.e. checking) logic that you need to do, before allowing a new value to be assigned. Rewriting your car class to demonstrate a check:

    public class Car
    {
        public string? Color { get; private set; }
        public int? Year { get; private set; }
    
        public void Update(string? arg_color = null, int? arg_year = null)
        {
            // Prevent assigning a combination of values that we know are wrong.
            if (arg_color != "Black" && arg_year < 1910)
            {
                throw new InvalidOperationException("All cars before 1910 were black");
            }
    
            // Or else, assign the values.
            Color = arg_color;
            Year = arg_year;
        }
    }
    

    You'll notice also that I've made the properties Color and Year into private set. This is because we're using an Update method, so there's no point having set available to publicly use. We have deliberately prevented anyone from directly setting those values, so that they are forced to use our Update method. You now set the values like this:

    myCar.Update("Blue", 2024);
    
    myCar.Color = "Blue"; // <- this no longer works because we have a private setter. The compiler will not allow it.
    

    Choosing between the above approaches

    Typically, you would either have an Update method, or you would have set methods of your properties. There's no point mixing both, and it will confuse you (and other developers) because it makes it unclear whether they should choose Update or set.

    • If you have a class with no validation logic and basically just a bunch of values: use the set approach.
    • If you have a class that has logic in it, or complex behaviours when updating values: have methods instead.

    2. Creating new objects

    The wrong way (your code)

    The second concept you've mixed up a bit is this:

        public Car Update(string arg_color = null, int? arg_year = null)
        {
            Car newCar = new Car();
            // ...snipped code for brevity...
    
            return newCar;
        }
    

    What you're doing here is creating a totally new car object, and returning it. You're not modifing or updating the original car at all.

    So, both the Update method and the set methods described above, are not altering your car. You're literally creating a new car. Not updating one. If you try this in your code, you'll see this happens:

    Car thisCar = new Car() { Color = "Red", Year = 2024 };
    
    thisCar.Update("Blue", 2000);
    
    Console.WriteLine(thisCar.Year); // <- this will still be "2024"
    Console.WriteLine(thisCar.Color); // <- this will still be "Red"
    

    So it will look like nothing has changed. In reality, what you've done in Update is create a totally new Car, without realising it, which has the new values. You could prove it by trying this:

    Car thisCar = new Car() { Color = "Red", Year = 2024 };
    Car newCar = thisCar.Update("Blue", 2000);
    
    Console.WriteLine(thisCar.Year); // <- this will still be "2024"
    Console.WriteLine(thisCar.Color); // <- this will still be "Red"
    
    Console.WriteLine(newCar.Year); // <- this will be "2000"
    Console.WriteLine(newCar.Color); // <- this will be "Blue"
    

    This is an odd mix. You've got an Update method that doesn't actually update the object, and returns something new instead.

    A valid way (and one possible answer to your question)

    There is a valid coding pattern where you can change some data (like modifying the colour) but safely leaving the original data intact, and returning a new copy. In C# this is done with record and it looks like this:

    public record Car(string? color, int? year);
    

    This is basically identical to your Car class, but much shorter. It has two properties and (as you'll see next) a way to update it.

    So now, to finally answer your question: here you can take a Car, modify some properties using the with keyword, and the other properties are all copied forward:

    Car redCar = new Car("Red", 2024);
    Car blueCar = redCar with { Color = "Blue" };
    
    Console.WriteLine(redCar.Color); // <- this will still be Red
    Console.WriteLine(blueCar); // <- this will now be Blue, but also 2024
    

    Note if you want to do validation, like in my Update example earlier, then you can still add an Update method to the record. I won't put an example here, but you can do some reading around record^1 and see how to do that.

    Hope that helps.