0

Imagine a simple POCO

public class Test{
    public int ID {get; set;}
    public string Name {get; set;}
    public string SomeProperty {get; set;}
}

Is there a way to wire this object up such that an event will fire only when all properties have been set? Like an InitializeComplete event or something? Or alternately is there a way to easily create such an event custom?

thanks

snappymcsnap
  • 2,050
  • 2
  • 29
  • 53
  • 1
    If you allow events in POCO, then adding some code to setter should be fine too. – Sinatr Nov 06 '18 at 15:14
  • 1
    [ISupportInitialize Interface](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.isupportinitialize?view=netframework-4.7.2) but it is more for use with controls. In order for it to be useful, the actor creating the object has to call `EndInit` which is unlikely with DTOs and the like – Ňɏssa Pøngjǣrdenlarp Nov 06 '18 at 15:15
  • 3
    X-Y-Question. Why do you think you need such an event? – Fildor Nov 06 '18 at 15:23
  • Have you considered INotifyPropertyChange in POCO? You'll still need a method to perform the checking when PropertyChange fires. – nocturns2 Nov 06 '18 at 16:25
  • These are a couple of links: https://learn.microsoft.com/en-us/dotnet/framework/winforms/how-to-implement-the-inotifypropertychanged-interface and https://www.codeproject.com/Articles/141732/Automatic-Implementation-of-INotifyPropertyChanged – nocturns2 Nov 06 '18 at 16:29

4 Answers4

2

You could implement this by yourself like so:

public delegate void AllPropertiesSetDelegate();
public class Test
{
    public delegate void AllPropertiesSetDelegate(object sender, EventArgs args);

    public int Id
    {
        get => _id;
        set
        {
            _id = value;
            CheckAllProperties();
        }
    }
    private int _id;
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            CheckAllProperties();
        }
    }
    private string _name;

    private void CheckAllProperties()
    {
        //Comparing Id to null is pointless here because it is not nullable.
        if (Name != null && Id != null)
        {
            AllPropertiesSet?.Invoke(this, new EventArgs());
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Test t = new Test();
        t.AllPropertiesSet += delegate { AllPropsSet(); };
        t.Id = 1;
        t.Name = "asd";
        Console.ReadKey();
    }

    static void AllPropsSet()
    {
        Console.WriteLine("All properties have been set.");
    }
}

See for yourself if you can get the implementation smaller/less painfull to deal with.

Test code:

class Program
{
    static void Main(string[] args)
    {
        Test t = new Test();
        t.AllPropertiesSet += delegate { AllPropsSet(); };
        t.Id = 1;
        t.Name = "asd";
        Console.ReadKey();
    }

    static void AllPropsSet()
    {
        Console.WriteLine("All properties have been set.");
    }
}

Heres how you could use reflection to check all non-value types for null:

    public static bool AllPropertiesNotNull<T>(this T obj) where T : class
    {
        foreach (var prop in obj.GetType().GetProperties())
        {
            //See if our property is not a value type (value types can't be null)
            if (!prop.PropertyType.IsValueType)
            {
                if (prop.GetValue(obj, null) == null)
                {
                    return false;
                }
            }
        }
        return true;
    }

You can consume this in the original code by modifying the CheckAllProperties method:

    private void CheckAllProperties()
    {
        if (this.AllPropertiesNotNull())
        {
            AllPropertiesSet?.Invoke(this, new EventArgs());
        }
    }
fstam
  • 669
  • 4
  • 20
  • @RandRandom Thanks, fixed. – fstam Nov 06 '18 at 15:24
  • 1
    `Name != null && Id != null` is a support nightmare. As an idea: use `[CallerMemberName]`, reflection and some hash-table. – Sinatr Nov 06 '18 at 15:25
  • You probably want add the standard event signature (object sender, EventArgs args) to the event. And to follow along with what @Sinatr said, you want something more positive than a double null check. Perhaps a `[Flags]` enum parameter to 'CheckAllProperties` – Flydog57 Nov 06 '18 at 15:26
  • @Flydog57 Added. – fstam Nov 06 '18 at 15:28
  • @Sinatr Not sure how you would implement that. This code is readable, that usually has my preference. Anyway feel free to submit an edit for the code! – fstam Nov 06 '18 at 15:29
  • This seems like a good answer to me. I don't know why it got downvoted. You get +1 from me. – JuanR Nov 06 '18 at 15:45
  • @JuanR Why upvote an answer that doesn't even have compiling code? You should reserve your upvotes for answers that actually work. – Servy Nov 06 '18 at 16:14
  • @Servy: I don't think we are here to write perfect code in our answers. Some of this stuff comes right out of our heads without a code editor or compiling tool at hand. In my mind, what matters most is the idea we are trying to convey. The concept behind the solution. Anyone can correct a typo here and there and get it to work. Still, a matter of opinion. That's why it's a "vote" and to me, this answer is useful. – JuanR Nov 06 '18 at 16:19
  • @JuanR But the idea is a really poor idea, for the reasons already stated. Why do you think this is a good idea? And it's not like there's a typo, the problems with the answer are *fundamental*. – Servy Nov 06 '18 at 16:23
  • @Servy I agree that the code should not be used. Thats not what OP asked though. You should never need this type of code because you are doing something else wrong. That doesn't make this a non-answer though. I actually did write this is an editor and it compiles and runs fine so instead of youf bullying, tell me your error or contribute with your own answer. – fstam Nov 06 '18 at 16:29
  • @fstam So you posted an answer that you think is a bad answer to the question. That's not a good thing to do. Why would you do something so mean, and why would you be upset that others are pointing out what you already know, that it's a bad answer? You should be more respectful of other people and not post answers that aren't useful. – Servy Nov 06 '18 at 16:33
  • @Servy: Just because you think it's a bad answer, it doesn't mean everyone else does. – JuanR Nov 06 '18 at 16:35
  • @JuanR The author says they think it's a bad answer. Your only argument for why you think it's a good answer is, "I'm allowed to vote however I want". Saying that no one can stop you from upvoting it isn't exactly a ringing endorsement of the answer's value. – Servy Nov 06 '18 at 16:38
  • @Servy: You don't know my argument. I have not given any. My comments are not meant to boost the validity of the post. They are meant to point out that you are making assumptions and drowning in a glass of water. I agree with the author of the answer that a question was asked and an answer was given, regardless of whether it was the right question to ask. – JuanR Nov 06 '18 at 16:53
  • @juanR I appreciate you defending me, I really do. But I do think it's better for your own well-being to just let this go. This argument isn't doing anyone any good :) – fstam Nov 06 '18 at 17:06
1

If you want to ensure that an object is properly created, why not make it so the only way to create it is to also set all the properties.

public class Test{
    public int ID {get; set;}
    public string Name {get; set;}
    public string SomeProperty {get; set;}

    // Constructor
    public Test(int id, string Name, string someProperty)
    {
        this.ID = id;
        this.Name = name;
        this.SomeProperty = someProperty;
    }
}
Robin Bennett
  • 3,192
  • 1
  • 8
  • 18
  • This is certainly a good pattern and how I code my own classes too. However, there are legitimate cases when we need to start with default property values and check for changes down the road so I am not sure this addresses the OP's requirement. – JuanR Nov 06 '18 at 16:38
0

Here's a variation on @fstam's answer. It changes this to a full on event and makes the call to check the properties cleaner. If you upvote me, upvote @fstam.

First the class:

public class TestPropertyCheck
{
    public event EventHandler AllPropertiesSet;

    public int Id
    {
        get => _id;
        set
        {
            _id = value;
            CheckAllProperties(PropertyNames.Id);
        }
    }
    private int _id;
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            CheckAllProperties(PropertyNames.Name);
        }
    }
    private string _name;

    public string Address
    {
        get => _address;
        set
        {
            _address = value;
            CheckAllProperties(PropertyNames.Address);
        }
    }
    private string _address;

    private void CheckAllProperties(PropertyNames propName)
    {
        propertiesSet |= propName;
        if (propertiesSet == PropertyNames.AllProps)
        {
            AllPropertiesSet?.Invoke(this, new EventArgs());
        }

    }

    private PropertyNames propertiesSet = PropertyNames.None;

    [Flags]
    private enum PropertyNames
    {
        None = 0,
        Id = 0x01,
        Name = 0x02,
        Address = 0x04,
        AllProps = Id | Name | Address,
    }
}

Then a test program

public static class PropertyCheckTester
{
    public static void Test()
    {
        var test = new TestPropertyCheck();
        test.AllPropertiesSet += AllPropertiesSet;
        Debug.WriteLine("Setting Name");
        test.Name = "My Name";
        Debug.WriteLine("Setting ID");
        test.Id = 42;
        Debug.WriteLine("Setting Address");
        test.Address = "Your address goes here";

    }

    public static void AllPropertiesSet(object sender, EventArgs args)
    {
        Debug.WriteLine("All Properties Set");
    }
}

And the output:

Setting Name
Setting ID
Setting Address
All Properties Set
Flydog57
  • 6,851
  • 2
  • 17
  • 18
  • I see, that works too. I kind of dislike the need for the enum though – fstam Nov 06 '18 at 16:10
  • Maybe you can use reflection to get all nullable properties of a class and check them for null. That would make it more generic. – fstam Nov 06 '18 at 16:12
  • Try with this: https://stackoverflow.com/questions/22683040/how-to-check-all-properties-of-an-object-whether-null-or-empty – fstam Nov 06 '18 at 16:14
  • The enum is *way* cheaper than using reflection. Yes, it requires maintenance (if you add a property, you need to do two things to the enum), but, that's all hidden within the class. – Flydog57 Nov 06 '18 at 16:20
0

Such event is fairly easy to implement for normal classes with full properties (code below is from answer of @fstam and partially from head, tell me if something is wrong, or threat it as pseudo-code):

public class Test
{
    public EventHandler InitializeCompleted;

    int _id;
    public int Id
    {
        get => _id;
        set
        {
            _id = value;
            CheckAllProperties();
        }
    }

    string _name;
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            CheckAllProperties();
        }
    }

    HashSet<string> _initialized = new HashSet<string();

    void CheckAllProperties([CallerMemberName] string property = null)
    {
        if(_initialized == null) // ignore calls after initialization is done
            return;
        _initialized.Add(property);
        if(_initialized.Count == 2) // all properties setters were called
        {
            _initialized = null;
            InitializeCompleted?.Invoke(this, EventArgs.Empty);
        }
    }
}

Using reflection can make the task even simpler: you can get property counter (no need to maintain that number in CheckAllProperties), mark properties which has to be included/excluded. If you decide to do so, don't forget to use lazy pattern, to only do it once for type, not for each instance.

Sinatr
  • 20,892
  • 15
  • 90
  • 319