I need something akin to C++'s shared_ptr. Essentially I need an IDisposable item that I can ensure is cleaned up as soon as possible once all references have been removed, but this resource will be shared between many classes.
I don't allow passing in of a an already existing object as it's an easy way to know that the item I'm using isn't being hasn't already been disposed.
Unfortunately I can't think of a way to prevent the user of Item from disposing the underlying item, but as this is to go into my own program I'm not too concerned about that because it intuitively makes sense to not dispose something within a wrapper, provided every class that needs the item uses the wrapper and not just the contained item.
I simply want to know if anyone can see any potential issues with this solution besides what's mentioned.
public class SharedDisposable<T> : IDisposable where T : class, IDisposable {
static SharedDisposable(){
instances = new Dictionary<T, List<SharedDisposable<T>>>();
}
public SharedDisposable(SharedDisposable<T> share) {
Item = share.Item;
instances[Item].Add(this);
}
public SharedDisposable(object[] ctorArgs = null) {
if(ctorArgs == null)
ctorArgs = new object[] { };
var ctor = typeof(SharedDisposable<T>).GetConstructor(ctorArgs.Select(a => a.GetType()).ToArray());
Item = (T)ctor.Invoke(ctorArgs);
instances[Item] = new List<SharedDisposable<T>>() { this };
}
public SharedDisposable(KeyValuePair<Type, object>[] ctorArgs) {
var ctor = typeof(SharedDisposable<T>).GetConstructor(ctorArgs.Select(a => a.Key).ToArray());
Item = (T)ctor.Invoke(ctorArgs.Select(a => a.Value).ToArray());
instances[Item] = new List<SharedDisposable<T>>() { this };
}
public void Dispose() {
var instanceList = instances[Item];
instanceList.Remove(this);
if(instances.Count == 0) {
instances.Remove(Item);
Item.Dispose();
}
}
public T Item { get; }
static Dictionary<T, List<SharedDisposable<T>>> instances;
}
Example usage:
class MainForm : Form {
public MainForm(){
InitializeComponent();
Port = new SharedDisposable<SerialPort>("COM5", 9600);
}
public MainForm(SharedDisposable<SerialPort> share){
InitializeComponent();
if(share == null)
throw new ArgumentNullException(nameof(share))
Port = new SharedDisposable<SerialPort>(share);
}
public void BtnDevice1Info_Click(object sender, EventArgs e){
new Device1InfoForm(Port).Show();
}
public void BtnDevice2Info_Click(object sender, EventArgs e){
new Device2InfoForm(Port).Show();
}
override Dispose(bool disposing){
if(disposing){
Port.Dispose();
}
base.Dispose(disposing);
}
private SharedDisposable<SerialPort> Port { get; }
}
In my application I actually have a Modbus interface instead of serial port but I just realised the way the constructor is setup won't allow me to use the Modbus API because the Modbus API uses factories instead of constructors, so I'll need to adjust it for that. But that API should be able to queue messages to be sent out one at a time, preventing any conflicts on the serial port while also allowing multiple forms or even applications to use the same serial port. In order to make this happen I need multiple forms to be able to share a disposable resource, but none of them to clean it up unless they're the last holder of that resource.
MainForm
. Closing it exits the application. \$\endgroup\$Application.Run(new MainForm())
will exit once theMainForm
gets closed.Application.Run()
or a manual loop withApplication.DoEvents()
can keep running even when theMainForm
gets closed. \$\endgroup\$