【问题标题】:Is there an event for when all properties of an object have been set?当对象的所有属性都已设置时,是否有事件?
【发布时间】:2018-11-06 15:12:57
【问题描述】:

想象一个简单的 POCO

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

有没有办法将这个对象连接起来,以便只有在设置了 所有 属性时才会触发事件?像 InitializeComplete 事件之类的?或者有没有办法轻松创建这样的事件自定义?

谢谢

【问题讨论】:

标签: c# .net events


【解决方案1】:

你可以像这样自己实现:

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.");
    }
}

你自己看看你是否可以让实现更小/更容易处理。

测试代码:

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.");
    }
}

以下是如何使用反射检查所有非值类型是否为空:

    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;
    }

您可以通过修改 CheckAllProperties 方法在原始代码中使用它:

    private void CheckAllProperties()
    {
        if (this.AllPropertiesNotNull())
        {
            AllPropertiesSet?.Invoke(this, new EventArgs());
        }
    }

【讨论】:

  • @RandRandom 谢谢,已修复。
  • Name != null &amp;&amp; Id != null 是支持的噩梦。作为一个想法:使用[CallerMemberName]、反射和一些哈希表。
  • 您可能希望将标准事件签名(对象发送者、EventArgs args)添加到事件中。并遵循@Sinatr 所说的内容,您需要比双重空检查更积极的东西。也许是 [Flags] 'CheckAllProperties` 的枚举参数
  • @Flydog57 已添加。
  • @Sinatr 不确定你将如何实现它。这段代码是可读的,这通常是我的偏好。无论如何,请随时提交代码编辑!
【解决方案2】:

如果你想确保一个对象被正确创建,为什么不这样做,所以创建它的唯一方法就是同时设置所有属性。

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;
    }
}

【讨论】:

  • 这当然是一个很好的模式,也是我编写自己的类的方式。但是,在某些情况下,我们需要从默认属性值开始并检查未来的变化,所以我不确定这是否满足 OP 的要求。
【解决方案3】:

这是@fstam 答案的一个变体。它在event 上将其更改为完整,并进行调用以更清晰地检查属性。如果你支持我,请支持@fstam。

第一节课:

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,
    }
}

然后是一个测试程序

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");
    }
}

还有输出:

Setting Name
Setting ID
Setting Address
All Properties Set

【讨论】:

  • 我明白了,这也有效。我有点不喜欢枚举的需要
  • 也许您可以使用反射来获取类的所有可为空的属性并检查它们是否为空。这会让它更通用。
  • 枚举比使用反射便宜方式。是的,它需要维护(如果你添加一个属性,你需要对枚举做两件事),但是,这一切都隐藏在类中。
【解决方案4】:

对于具有完整属性的普通类来说,这样的事件相当容易实现(下面的代码来自@fstam 的回答,部分来自头部,告诉我是否有问题,或者以伪代码威胁它):

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);
        }
    }
}

使用反射可以使任务更加简单:您可以获得属性计数器(无需在CheckAllProperties 中维护该数字),标记必须包含/排除的属性。如果您决定这样做,请不要忘记使用惰性模式,只对类型执行一次,而不是对每个实例执行一次。

【讨论】:

  • 这可能是最好的方法。它很干净,不需要枚举。
猜你喜欢
  • 2014-04-04
  • 2018-12-02
  • 2019-07-18
  • 1970-01-01
  • 1970-01-01
  • 2015-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多