-1

i.e.

MyClass myClass = new MyClass() { Value = 5 };

I have a bunch of constructor calls like the one above, but now I've realized I need to add logic to the constructor, which was a massive oversight. Currently I have no constructor, so just a blank implicit default constructor.

The below code should explain my problem.

Edit: I'm not actually doing validation, that's just a simple example of constructor logic

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new Test(1) + " should be true");
        Console.WriteLine(new Test(0) + " should be false");
        Test test = new Test(0) { Value = 1 }; // It allows this syntax, oddly, but the value that's used is the one passed as a parameter
        Console.WriteLine("I wish " + test + " was true");
        // This is what I have currently, but I'd like to add logic like that which exists in the parameterized constructor
        //Test test = new Test() { Value = 1 } // Would ideally function just like Test(1), otherwise I have to go and change every call

        // OUTPUT
        // True should be true
        // False should be false
        // I wish False was true

        Console.ReadLine();
    }
}

class Test
{
    public bool? IsGood { get; }
    public int Value { get; set; }

    // This doesn't currently exist in my class, but I'd like to add it
    public Test(int value)
    {
        if (value == 1)
            IsGood = true;
        else
            IsGood = false;
    }

    public override string ToString()
    {
        return IsGood.ToString();
    }
}
Sinjai
  • 1,085
  • 1
  • 15
  • 34
  • 3
    That is [object initializer](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers) syntax, and it happens after the constructor finishes. What is the point of the `Value` property? You're not using it in the constructor--i.e. you named the parameter with the same name, which means it hides the property. – Kenneth K. Jul 07 '17 at 22:02
  • You could write a setter for `Value` which would change `IsGood` then it is set. For that you would also need to have a private setter for `IsGood`. Or it is something you can't do? – user1242967 Jul 07 '17 at 22:04
  • @user1242967 Using a setter is an excellent idea. – Sinjai Jul 07 '17 at 22:09
  • This code is super confusing; why is there a parameter named `Value`? Why is the `Value` property that it hides never set? Suppose that `Value` is set to an invalid value later; `IsGood` never changes! This seems like a complete mess. – Eric Lippert Jul 07 '17 at 22:17

2 Answers2

4

Don't write code like this in the first place.

I'd write your code like this:

class Test 
{
  public static bool IsValid(int value) 
  {
     return whatever; // test for validity here
  }
  public int Value { get; private set; } // Don't let anyone change it.
  public Test(int value) {
    if (!IsValid(value)) throw new InvalidArgumentException("value");
    this.Value = value;
  }
}

There, now Value is always valid; the user can know ahead of time whether it is valid or not; an attempt to set an invalid value produces an exception. This assumes that Value cannot change.

If Value can change then write it like this:

class Test 
{
  public static bool IsValid(int value) 
  {
     return whatever; // test for validity here
  }
  private int value;
  public int Value { get { return value; } 
    set 
    {
      if (!IsValid(value)) throw new InvalidArgumentException("value");
      this.value = value;
    }
  }
  public Test(int value) {
    this.Value = value;
  }
}

Now the value is again always legal.

If it is legal for value to be invalid, then:

class Test 
{
  public bool IsValid 
  {
     get 
     {
       return whatever; // test for validity here
     } // read-only property
  }
  public int Value { get; set; }
  public Test(int value) {
    this.Value = value;
  }
}

Now the value can be any integer and whether it is valid or not can be tested dynamically.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • If Value is immutable I would further make this a struct rather than a class. Are there situations where this would not be acceptable? – Justin Blakley Aug 11 '17 at 16:04
  • @JustinBlakley: not all immutable, value-like types can be value types. String, for example, is a reference type even though strings are immutable and logically values. Value types must also be all the same size, ideally a small size. Strings don't meet that criterion. – Eric Lippert Aug 15 '17 at 19:58
  • I can't believe I forgot about the most CS 101 data structure out there ;). Would you agree though that, in this example, a struct would be advisable since it is essentially a wrapper around int? – Justin Blakley Aug 16 '17 at 19:47
  • @JustinBlakley: In this case yes, though based on the "abstract" nature of the example -- "Test", "Value", and so on are pretty vague -- it is hard to say whether the "real world" data type is actually a wrapped int or something more complicated. – Eric Lippert Aug 16 '17 at 19:54
  • @EricLippert A value type holding a reference to a char array is always the same size, isn't it? I can't see that being the reason why strings are reference types and not value types. Am I missing something? – InBetween Aug 16 '17 at 23:59
  • What's the germane difference between a value type containing a single reference and a reference? – Eric Lippert Aug 17 '17 at 01:35
  • What I'm getting at here is: *references* already have value semantics. Making a value type that contains a single reference buys you nothing but overhead, confusion, and inability to make reference comparisons; you'd be better off just making it a reference, which is what strings are. – Eric Lippert Aug 17 '17 at 14:09
  • @EricLippert Understood it with your first comment ;) yeah, if the value type is made up of a single reference, it makes no sense at all, should have thought it through a little bit more. – InBetween Aug 17 '17 at 22:52
0

Can I set members outside of a constructor while still using logic in the constructor?

Meaning, what, exactly?

Using the object initializer syntax, a constructor still runs. You may even choose which one to use, through the normal constructor overload syntax (which you seem to show, but you say it's not in your class?). The code in your constructor looks at the parameter value that is passed to it, not the property Value (which it doesn't even set). But if you meant for the two to work together, then sure…you can set the property in the constructor and set IsGood in the Value property setter.

If you're going to do it that way, then I would not bother with the logic in the constructor at all. Just set the Value property and let its setter do the rest of the work:

class Test
{
    public bool? IsGood { get; private set; }

    private int _value;
    public int Value
    {
        get { return _value; }
        set
        {
            _value = value;
            IsGood = _value == 1;
        }
    }

    public Test(int value)
    {
        Value = value;
    }
}

I should point out that the semantics of the above is slightly different from what you seem to have started with. That is, the Value property is not read-only, and so can be set at any time. So, similarly, the IsGood property can change at any time. You previously had declared it as read-only and it was settable only in the constructor.

It's not clear from your question whether that's a problem or not. If you want IsGood to be strictly read-only (i.e. without even a private setter), then it won't be possible to do literally what you're asking for, because in the object initializer syntax, it relies on setting member properties after the constructor has already returned.

For the moment, I'll assume it's not a problem to add the private setter to the IsGood property.

Note that since IsGood apparently depends solely on the value of Value, you could even implement the above like this:

class Test
{
    public bool? IsGood => _value != null ? _value == 1 : (bool?)null;

    private int? _value;
    public int Value
    {
        get { return _value ?? 0; }
        set { _value = value; }
    }

    public Test(int value)
    {
        Value = value;
    }
}

That is, don't even bother storing a value for IsGood. Just return the appropriate value based on the current state of the Value property (null if it's never been set, true if it's currently set to 1, and false otherwise).

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • That's a typo, definitely meant to have the parameter named lowercase. I overlooked using a setter because I'm looking to do something only when the object is constructed. But luckily the property in question only gets modified during construction, so it looks like that is the answer. – Sinjai Jul 08 '17 at 06:20
  • One thing that is not clear in your question is, do you really need to use the object initializer syntax? If you are okay with setting the property only in the constructor, i.e. passing the value to the constructor instead of using the object initializer syntax, then you don't even need a public setter on the property. Passing the value to the constructor _and_ also setting the property explicitly in the object initializer block seems redundant to me. – Peter Duniho Jul 08 '17 at 06:24
  • I wouldn't be doing both, that line is just meant to show that I want the same logic to be run as when calling the parameterized constructor. And yes, I need to use it, as I have a bunch of object initializer calls already that I'd have to change, and some have more than 10 parameters, so seeing exactly which property is being set is helpful for readability. – Sinjai Jul 08 '17 at 06:27
  • Okay, just making sure. The code above illustrates what you'll need...I'll assume you'll manage the potential redundancy on your own and ensure that the code is actually used in a sensible way. :) – Peter Duniho Jul 08 '17 at 06:29
  • Yessir. Though I'm not familiar with the syntax used to define the bool in the second example. Can you not use `=` there? I'll mark an answer after I get some sleep, it's 1:30. You think it would be appropriate to remove the bit about my typo from your answer, given that I've fixed the question and it would just muddy the water for a potential future viewer? – Sinjai Jul 08 '17 at 06:35
  • The `bool?` property is declared using the ["expression body"](https://stackoverflow.com/q/31764532) syntax. Yes, I guess it would be confusing to readers to see the comment about the parameter name now that you've changed your code in the question. – Peter Duniho Jul 08 '17 at 06:37
  • Question though, if I have properties `a` and `b` and I use a's value in b's setter, do I need to make sure I declare a before b, as in `{a = 1, b = 2}`, not `{b = 2, a = 1}`? – Sinjai Jul 08 '17 at 06:38
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/148667/discussion-between-peter-duniho-and-sinjai). – Peter Duniho Jul 08 '17 at 06:39