将对一个对象字段的引用传递并存储到另一个对象而不进行反射

本文关键字:一个对象 反射 存储 字段 引用 | 更新日期: 2023-09-27 18:31:02

在Lua中,由于对象通常实现为哈希表(如JS),因此我可以使用以下签名编写补间函数(或者更确切地说,它已经被编写了):

timer.tween(delay_in_s, object, table_of_target_values, algorithm)

timer.tween将能够访问table_of_target_values中枚举object字段以保存其初始值。 然后,timer.update还可以引用它们并设置它们的值。

如果是值类型,有没有办法在 C# 中保存对类字段的引用?我知道你不能通过引用传递属性(虽然你可以在 VB.NET 中传递),但字段是可以的。但是,如何在不使用反射的情况下保存它以供以后使用呢?

一种解决方法是传递 setter 闭包而不是对象本身,因此签名如下所示:

Timer.Tween<T>(double delay, T initialValue, T targetValue, Action<T> setter, Algorithm algorithm)

并像这样称呼它:

Timer.Tween(delay, obj.fld, targetValue, (x)=>obj.fld=x, Algorithm.Linear);

这是唯一的选择吗?

编辑:为了澄清我的情况,有Timer.Update(double delta)更新Timer.Tween()调用中引用的所有变量,直到它们达到目标值,这不仅仅是传递对Timer.Tween()的引用的问题。

将对一个对象字段的引用传递并存储到另一个对象而不进行反射

可以使用 ref 关键字在 C# 中使用引用调用:

public void Swap(ref int a, ref int b) 
{
    int c = b;
    b = a;
    a = c;
}
public void DoSwap()
{
   int x = 1;
   int y = 2;
   Console.WriteLine(x + " "  + y); // should write 1 2
   Swap(ref x, ref y);
   Console.WriteLine(x + " "  + y); // should write 2 1
}

现在,仅仅因为您可以这样做并不意味着您应该这样做。通常,您应该尝试适应该语言的常见习语并接受这些习语,而不是通过ref几乎每种方法。

我决定继续我的闭包方法,它看起来还不错,我什至会说它看起来很优雅。

/// <summary>
/// A static class that manages all active tweens.
/// </summary>
public abstract class Tween
{
    private static HashSet<Tween> tweens = new HashSet<Tween>();
    private Tween() {}
    public static void Add<T>(double delay, T initialValue, T targetValue, Action<T> setter, Algorithm algorithm, Action onCompletion)
    {
        var t = new TweenImpl<T>(delay, initialValue, targetValue, setter, algorithm, onCompletion);
        tweens.Add(t);
    }
    public static void Update(double delta)
    {
        var toRemove = new List<Tween>();
        foreach (var t in tweens) {
            if (t.UpdateTween(delta)) {
                toRemove.Add(t);
            }
        }
        foreach (var t in toRemove) {
            tweens.Remove(t);
        }
    }
    internal abstract bool UpdateTween(double delta);
    /// <summary>
    /// A class that represents tweens.
    /// </summary>
    private class TweenImpl<T> : Tween
    {
        private double delay;
        private double accumulatedDelta;
        private T initialValue;
        private T valueRange;
        private Action<T> setter;
        private Algorithm algorithm;
        private Action onCompletion;
        internal TweenImpl(double delay, T initialValue, T targetValue, Action<T> setter, Algorithm algorithm, Action onCompletion)
        {
            this.delay = delay;
            this.initialValue = initialValue;
            this.valueRange = Operator.Subtract(targetValue, initialValue);
            this.setter = setter;
            this.algorithm = algorithm;
            this.onCompletion = onCompletion;
            this.accumulatedDelta = 0.0;
            this.setter.Invoke(this.initialValue);
        }
        internal override bool UpdateTween(double delta) {
            var toRemove = false;
            this.accumulatedDelta += delta;
            if (this.accumulatedDelta >= this.delay){
                this.accumulatedDelta = this.delay;
                toRemove = true;
            }
            var percentage = (double)this.accumulatedDelta/this.delay;
            percentage = this.algorithm.Adjust(percentage);
            var result = Operator.AddAlternative(this.initialValue, Operator.MultiplyAlternative(percentage, this.valueRange));
            this.setter.Invoke(result);
            if (toRemove) {
                this.onCompletion.Invoke();
            }
            return toRemove;
        }
    }
}

我遇到的唯一问题是你不能将 T 限制为数字类型,所以我使用 John Skeet 的 MiscUtil 在运行时生成 lambda。当然,这意味着如果有人试图补间字符串,程序将崩溃,但这是我愿意忍受的。

你这样称呼它(我在进度条上测试了这个):

Tween.Add(15.0, prgTest.Minimum, prgTest.Maximum, x=>prgTest.Value=x, Algorithm.Linear, ()=>{
                            running = false;
                            MessageBox.Show("Done");
                         });

然后简单地打电话

Tween.Update(delta);

以自动补间变量。