参考

Observable、ObservableList 和 ObservableDictionary 的每个成员,以及各自会通知什么。

更新于 2026-09-08

所有内容都在 Udonite.Observables 中。

Observable<T>

new Observable<int>()        // 默认值,不通知任何人
new Observable<int>(7)       // 带初值,不通知任何人
成员 是否通知
T Value { get; set; } 设置时,且仅当新值不同
Subscribe(Action<T> handler)
Subscribe(Action<T> handler, bool notifyNow) notifyNow 只调用这一个处理器
Unsubscribe(Action<T> handler) 否;移除一个从未订阅的处理器不会发生任何事
UnsubscribeAll()
SetSilently(T value)
Publish() 是,通知每个订阅者,无论有没有变化
bool CascadeStalled { get; }
ToString() 否;返回值本身,没有值时返回 ""

相等性用的是 object.Equals 两个相等的字符串不算变化;null 和别的值一样,所以从 nullnull 是安静的,从 null 到某个值则不是。

订阅两次只算一次。 通常是 Start 又跑了一遍,而每次变化被调用两次一定是个 bug。

订阅者可以在任何地方。 score.Subscribe(display.Show) 能到达另一个 behaviour 上的处理器,score.Subscribe(OnChanged) 到达本 behaviour 上的处理器,而不是 behaviour 的普通类则在自己内部订阅自己的方法。一个 Observable 可以同时持有这三种。

例外是把别的对象的方法交出去,而那个对象不是 behaviour:tally 是普通类时的 score.Subscribe(tally.Add) 会被拒绝,因为 Udon 没有地方安放接收者。让那个类自己订阅:

public class Tally
{
    public void Watch(Observable<int> score) { score.Subscribe(Add); }

    private void Add(int value) { ... }
}

没有订阅者数量,Unsubscribe 也不告诉你结果。 移除是按内容匹配的,所以可以用当场新建的委托取消订阅,不必是当初添加的那一个;但 Udon 没有注册任何查看订阅者列表的手段。需要知道自己订阅了什么的代码,请自己记录。

CascadeStalled 报告的是一次重启十六轮仍未收敛的 publish。要走到那一步需要一个会回写同一个 observable 的订阅者,而编译器拒绝这种写法,所以在世界里它始终是 false

ObservableList<T>

订阅者签名是 Action<ObservableList<T>>,拿到的是列表本身。

成员 是否通知 LastChange
T this[int] { get; set; } 设置时,且仅当不同 Replaced
Add(T) Added
AddRange(T[]) 整段只通知一次;空或 null 则不通知 None
Insert(int, T) Added
bool Remove(T) 仅当原本存在 Removed
RemoveAt(int) Removed
Clear() 仅当原本非空 Cleared
AddSilently(T)
ContainsIndexOfToArrayCount

在处理器内部,LastChangeLastIndexLastItem 描述发生了什么,订阅者因此可以只生成一个对象,而不必整个重建:

private void OnNamesChanged(ObservableList<string> list)
{
    if (list.LastChange == CollectionChange.Added)
        Spawn(list.LastItem);
    else
        RedrawEverything(list);
}

它们描述的是正在进行的这一次通知,只有在处理器内部才有意义。

ObservableDictionary<TKey, TValue>

订阅者签名是 Action<ObservableDictionary<TKey, TValue>>

成员 是否通知 LastChange
TValue this[TKey] { get; }
Set(TKey, TValue) 仅当值不同 新键为 Added,已有键为 Replaced
bool Remove(TKey) 仅当原本存在 Removed
Clear() 仅当原本非空 Cleared
SetSilently(TKey, TValue)
ContainsKeyTryGetValueCount

LastKeyLastValue 指出改变的是什么。

这里故意没有索引器 setter。 Udon 里的字典是 DataDictionary,读一个不存在的键会直接出错,而不是返回默认值;把一次读取藏在 setter 内部就成了陷阱。Set 让读取保持显式:先问 ContainsKey,或者用 TryGetValue

CollectionChange

NoneAddedRemovedReplacedCleared

None 同时表示"还什么都没发生"和"一次没有单一元素的批量变化":AddRange,或者一次在展示集合而非报告变化的 Subscribe(handler, true)

如果处理器抛出异常

在 Udon 里,一个异常会让 behaviour 在本次会话余下的时间里停摆,也没有办法只接住某个订阅者的异常再继续下一个。抛出异常的处理器会把本轮剩下的通知、以及那个 behaviour 之后本该做的一切一并带走,所以处理器最好只做读值和更新这两件事。

出了点问题 重新加载