我有一个用户控件,它将inner
个对象的列表订阅到另一个组件中的对象。有时我想根据用户控件的属性设置更改删除inner
个对象。如果我盲目地从列表中删除它们而不取消订阅,我认为这将导致孤立的inner
对象的内存泄漏。
有些时候我可能无权访问订阅时的对象列表。例如,我订阅的项目列表可能已被更改,这是我取消订阅的原因之一。
我当然可以在我的代码中为每个内部对象添加对订阅对象的引用,但我很有兴趣看看是否有可以利用的内置机制。
更新#1:
汉斯要求一些代码。我编写了一个示例,我认为该列表清除时innerObject
未被释放。我认为这相当于将它们的引用设置为null。
/*
* Program that demonstrates that event subscribers stay alive after
* loosing scope.
*
* The question is asking if I don't have a reference to myObject is there a way to
* unsubscribe with what the innerObject knows natively.
*
* In the Unsubscribe() method I have examples of unsubscribing using a "known" myObject
* and one with a self reference to the myObject.
*
* The solution that uses IObserver and IObservable automates a way to store an explicit
* reference to the subscription holder.
*
*/
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
/// <summary>
/// Sample objects that subscribe to MyObject's Updated event
/// </summary>
public class InnerObject
{
public MyObject Subscribed { get; set; }
public void Updated(object sender, MyObjectUpdateEventArgs e)
{
Console.WriteLine(e.Data);
}
}
/// <summary>
/// Object that publishes the Updated event
/// </summary>
public class MyObject
{
private string data_;
public event EventHandler<MyObjectUpdateEventArgs> Updated;
public string Data { get { return data_; } set { SetMyData(value); } }
/// <summary>
/// Outputs the Data string
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Data;
}
/// <summary>
/// Event handler
/// </summary>
/// <param name="e"></param>
private void OnUpdate(MyObjectUpdateEventArgs e)
{
EventHandler<MyObjectUpdateEventArgs> handler = Updated;
if (Updated != null) {
handler(this, e);
}
}
private void SetMyData(string value)
{
if (Data != value) {
data_ = value;
OnUpdate(new MyObjectUpdateEventArgs(Data));
}
}
}
/// <summary>
/// EventArgs to provide updated MyData value;
/// </summary>
public class MyObjectUpdateEventArgs
{
public MyObjectUpdateEventArgs(string data)
{
Data = data;
}
public string Data { get; set; }
}
internal class Program
{
private static List<InnerObject> innerObjectsList = new List<InnerObject>();
private static void Main(string[] args)
{
MyObject myObject = new MyObject();
myObject.Data = "Hello World";
Console.WriteLine(myObject.ToString());
Console.ReadLine();
// Create the innerObjectts and subscribe
MakeNewInnerObjects(5, myObject);
// This will cause all of the inner objects to respond with myObjects Data string
Console.WriteLine("Assigning new data to myObject\n");
myObject.Data = "Hello InnerObjects";
// Shows they are responding, even though the list and the items are out of scope
Console.ReadLine();
// Uncomment to unsubscribe
// Unsubscribe(myObject);
innerObjectsList.Clear();
// Force garbage collection for our example
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Assigning new data to myObject\n");
myObject.Data = "Are you still there InnerObjects?";
// Shows they still exist if we don't unsubscribe
Console.ReadLine();
}
/// <summary>
/// Create a count of InnerObjects, subscribe them to myObject and add to the list
/// </summary>
/// <param name="count"></param>
private static void MakeNewInnerObjects(int count, MyObject myObject)
{
// Uncomment if you want to have the list go out of scope as well to show
// That the reference keeps them alive
//List<InnerObject> innerObjectsList = new List<InnerObject>();
for (int i = 0; i < count; i++) {
InnerObject innerObject = new InnerObject();
innerObject.Subscribed = myObject;
myObject.Updated += innerObject.Updated;
innerObjectsList.Add(innerObject);
}
}
// Unsubscribe the list of innerObJects from MyObject
private static void Unsubscribe(MyObject myObject)
{
// Two ways to unsubscribe, the first depends on knowing what we subscribbed to
// the second uses a stored reference to the object
foreach (InnerObject innerObject in innerObjectsList) {
// myObject.Updated -= innerObject.Updated;
innerObject.Subscribed.Updated -= innerObject.Updated;
}
}
}
}
答案 0 :(得分:1)
如果您为发布商实施IObservable
,则Subscribe
调用将返回订阅对象,该对象会在您销毁时取消订阅。像这样:
public class Observable<T> : IObservable<T>
{
protected readonly List<IObserver<T>> _subscribers = new List<IObserver<T>>();
private class Subscription : IDisposable
{
List<IObserver<T>> _subscribers;
IObserver<T> _observer;
public Subscription(List<IObserver<T>> subscribers, IObserver<T> observer)
{
_subscribers = subscribers;
_observer = observer;
}
public void Dispose()
{
_subscribers.Remove(_observer);
}
}
public IDisposable Subscribe(IObserver<T> observer)
{
_subscribers.Add(observer);
return new Subscription(_subscribers, observer);
}
}
Subscription
类完成断开连接的所有工作。这个难题的另一面是订阅者:
public abstract class Observer<T> : IObserver<T>, IDisposable
{
private IDisposable _subscription = null;
public void Dispose()
{
Unsubscribe();
}
public void Unsubscribe()
{
if (_subscription != null)
{
_subscription.Dispose();
_subscription = null;
}
}
public void SubscribeTo(IObservable<T> publisher)
{
Unsubscribe();
_subscription = publisher.Subscribe(this);
}
public virtual void OnCompleted()
{ }
public abstract void OnError(Exception error)
{ }
public abstract void OnNext(T value);
}
即使您没有使用IObservable<T>
和IObserver<T>
,基本原则也是一样的。您在发布者/ IObservable
跟踪订阅者,并分发可以从发布者列表中删除订阅者的订阅。订阅者只需跟踪他们的订阅。
更新:这是上述类的用法示例。
首先,一个方便的函数添加到Observable<T>
类:
public virtual void Publish(T value)
{
foreach (var sub in _subscribers.Distinct().ToArray())
{
try
{
sub.OnNext(value);
}
catch { }
}
}
这提供了一种简单的方法,用于通知所有订阅者发生了某些事情。现在,一个有点人为的例子:
public class KeyPublisher : Observable<ConsoleKeyInfo>
{
}
public class PrintKeys : Observer<ConsoleKeyInfo>
{
public override void OnNext(ConsoleKeyInfo next)
{
if (next.Modifiers != 0)
Console.Write("{0}-", next.Modifiers.ToString().Replace(", ", "-"));
Console.WriteLine(next.Key);
}
}
public class DetectEscape : Observer<ConsoleKeyInfo>
{
public bool FoundEscape { get; private set; }
public override void OnNext(ConsoleKeyInfo next)
{
if (next.Key == ConsoleKey.Escape)
FoundEscape = true;
}
}
class Program
{
static void Main(string[] args)
{
var pub = new KeyPublisher();
using (var sub1 = new PrintKeys())
using (var sub2 = new DetectEscape())
{
sub1.SubscribeTo(pub);
sub2.SubscribeTo(pub);
while (!sub2.FoundEscape)
{
pub.Publish(Console.ReadKey(true));
}
}
}
}
如果由于各种列表和订阅持有引用而导致对象生命周期出现问题,则解决方案可能是使用WeakReference
。这是使用弱引用的Subscription
类的一个版本:
private class Subscription : IDisposable
{
WeakReference<List<IObserver<T>>> _subscribers;
WeakReference<IObserver<T>> _observer;
public Subscription(List<IObserver<T>> subscribers, IObserver<T> observer)
{
_subscribers = new WeakReference<List<IObserver<T>>>(subscribers);
_observer = new WeakReference<IObserver<T>>(observer);
}
public void Dispose()
{
if (_subscribers != null && _observer != null)
{
List<IObserver<T>> subscribers;
IObserver<T> observer;
if (_subscribers.TryGetTarget(out subscribers) && _observer.TryGetTarget(out observer))
subscribers.Remove(observer);
_subscribers = null;
_observer = null;
}
}
}
您可以对_subscribers
列表执行相同操作(让它保持WeakReference<IObserver<T>>
)以阻止订阅者从垃圾回收中恢复...但我认为管理更好的做法IDisposable
个对象的生命周期。