【问题标题】:Invoke a method when the underlying value of a ref returning property changes当 ref 返回属性的基础值发生变化时调用方法
【发布时间】:2019-02-13 04:57:55
【问题描述】:

考虑以下代码:

public struct Vector2
{
   public float X;
   public float Y;
}

public class Sprite
{
   private Vector2 position;
   public ref Vector2 Position => ref position;

   private void DoStuffWhenPositionChanges() { /*...code...*/ }
}

ref return 允许我执行以下设置操作:

someSprite.Position.X++;
someSprite.Position.Y = 42;

我想在设置Position 结构的XY 组件时调用DoStuffWhenPositionChanges。注意,Vector2 是库级结构,不能在我的代码中更改。

我正在寻找类似的解决方案:

public class Sprite
{
   private Vector2 position;
   public Vector2 Position
   {
      get => position;  //ref return not possible!
      set
      {
         position = value;
         DoStuffWhenPositionChanges();
      }
   }

   private void DoStuffWhenPositionChanges() { /*...code...*/ }
}

...但使用ref return,以避免在调用代码中执行以下操作:

someSprite.Position = new Vector2(someSprite.Position.X, someSprite.Position.Y + 1);

我考虑过INotifyPropertyChanged,但由于我无法修改库级别的Vector2 结构,我需要一个替代解决方案。我还考虑了一种代理类型,可以隐式转换为 Vector2,它可以实现INotifyPropertyChanged,但这似乎……很麻烦;也许有一个我不知道的更清洁的解决方案。

根据上述设置,是否可以知道ref return 属性的基础值何时发生变化?

【问题讨论】:

  • 为什么不实现Translate() 方法?
  • Vector2 来自哪个框架/库?我不认为您要求的内容是可能的,但该框架可能会提供例如“已移动”事件,该事件将为您提供类似的信息。
  • Framework/library 并不真正相关,因为我正在寻找更通用的解决方案(实现Translate() 方法也是如此)。这个非常具体的例子是 MonoGame 框架;我没有标记 MonoGame,因为它恰好是我的问题的上下文。在这个非常特殊的情况下,我可以为 X 和 Y 组件提供传递属性;但它不适用于更大的结构。对于这个问题,我提供的示例恰好是minimal reproducible example
  • 这有点像在你提供了一个方法的响应之后,试图知道其他一些代码对 its 变量做了什么。在调试场景(例如数据断点)之外,您不会发现。

标签: c# c#-7.0


【解决方案1】:

一种方法是为 Struct 创建一个包装器,然后实现RaisePropertyChanged 方法或INotifyPropertyChanged

public struct Vector2
{
   public float X;
   public float Y;
}

public class VectorWrapper
{
   private Vector2 thing;
   public var X 
   {
        get { return thing.X; }
        set { thing.X = value; RaisePropertyChanged(SomePropertyName); }
   }
   public var Y 
   {
        get { return thing.Y; }
        set { thing.Y = value; RaisePropertyChanged(SomePropertyName); }
   }
}

【讨论】:

  • 是的,我在我的问题中提到了代理类型的想法,但它的问题是任何需要 Vector2 现在必须处理包装器......即使是隐式转换,它也是一个有点麻烦。
【解决方案2】:

这有点小技巧,但这可以完成工作。

static void Main(string[] args)
{
    var s = new Sprite();
    s.DoStuffWhenPositionChanges(s.Position.X++);

}

public struct Vector2
{
    public float X;
    public float Y;
}

public class Sprite
{
    private Vector2 position;

    public ref Vector2 Position => ref position;

    public void DoStuffWhenPositionChanges(float f = default)
    {

    }
}

【讨论】:

  • 这很有趣,虽然在功能上等同于在每次更改后运行该方法,因为该方法可以访问该点的数据。
  • 是的。不幸的是,你不能在 ref return 属性上设置 setter。