Mixing Covariance and Contravariance - c#

Goal:
Iterate over the following collection
var collection = new IImportTrigger<EventArgs>[]
{
new FileSystemImportTrigger()
, new TimerImportTrigger()
};
in this way
foreach (var trigger in collection)
{
trigger.Import += trigger.OnImport;
}
This is what I have so far
public delegate void ImportTriggerEventHandler<in T>(object sender, T args) where T : EventArgs;
public interface IImportTrigger<out T> where T : EventArgs
{
event ImportTriggerEventHandler<T> Import;
void OnImport<T1>(object sender, T1 args) where T1 : EventArgs;
}
public class FileSystemImportTrigger : IImportTrigger<FileSystemEventArgs>
{
public event ImportTriggerEventHandler<FileSystemEventArgs> Import;
public void OnImport<T>(object sender, T args) where T : EventArgs { }
}
public class TimerImportTrigger : IImportTrigger<ElapsedEventArgs>
{
public event ImportTriggerEventHandler<ElapsedEventArgs> Import;
public void OnImport<T>(object sender, T args) where T : EventArgs { }
}
Expectations:
I would like to define the IImportTrigger with only one generic parameter.
Problem:
If I change my Interface definition to the following (notice the generic argument T is not covariant any more).
public interface IImportTrigger<T> where T : EventArgs
{
event ImportTriggerEventHandler<T> Import;
void OnImport(object sender, T args);
}
and hence
public class FileSystemImportTrigger : IImportTrigger<FileSystemEventArgs>
{
public event ImportTriggerEventHandler<FileSystemEventArgs> Import;
public void OnImport(object sender, FileSystemEventArgs args) { }
}
public class TimerImportTrigger : IImportTrigger<ElapsedEventArgs>
{
public event ImportTriggerEventHandler<ElapsedEventArgs> Import;
public void OnImport(object sender, ElapsedEventArgs args) { }
}
I wont be able to create a common type for my collection
var collection = new IImportTrigger<EventArgs>[]
{
new FileSystemImportTrigger()
, new TimerImportTrigger()
};
because the Generic parameter is not output-safe any more.
Question:
Is there any way to accomplish my scenario?

By switching OnImport to be not generic at all then use a explicit interface, then make another more derived interface that is not covariant that has the generic verson of OnImport you could pull it off.
internal class Program
{
private static void Main(string[] args)
{
var collection = new IImportTriggerBase<EventArgs>[]
{
new FileSystemImportTrigger()
, new TimerImportTrigger()
};
foreach (var trigger in collection)
{
trigger.Import += trigger.OnImport;
}
}
}
public delegate void ImportTriggerEventHandler<in T>(object sender, T args) where T : EventArgs;
public interface IImportTriggerBase<out T> where T : EventArgs
{
event ImportTriggerEventHandler<T> Import;
void OnImport(object sender, EventArgs args);
}
public interface IImportTrigger<T> : IImportTriggerBase<T> where T : EventArgs
{
void OnImport(object sender, T args);
}
public class FileSystemImportTrigger : IImportTrigger<FileSystemEventArgs>
{
public event ImportTriggerEventHandler<FileSystemEventArgs> Import;
public void OnImport(object sender, FileSystemEventArgs args) { }
void IImportTriggerBase<FileSystemEventArgs>.OnImport(object sender, EventArgs args)
{
OnImport(sender, (FileSystemEventArgs)args);
}
}
public class TimerImportTrigger : IImportTrigger<ElapsedEventArgs>
{
public event ImportTriggerEventHandler<ElapsedEventArgs> Import;
public void OnImport(object sender, ElapsedEventArgs args) { }
void IImportTriggerBase<ElapsedEventArgs>.OnImport(object sender, EventArgs args)
{
OnImport(sender, (ElapsedEventArgs)args);
}
}
However this does give you the extra cruft of the OnImport(object sender, EventArgs args) method which is visible on IImportTrigger<T>.
That was to solve your problem, if I where going to do this and I am assuming correctly you just want derived classes to be able to pick up on the fact that Import is getting fired and you actually do not need OnImport exposed I would just do
internal class Program
{
private static void Main(string[] args)
{
var collection = new IImportTrigger<EventArgs>[]
{
new FileSystemImportTrigger()
, new TimerImportTrigger()
};
}
}
public delegate void ImportTriggerEventHandler<in T>(object sender, T args) where T : EventArgs;
public interface IImportTrigger<out T> where T : EventArgs
{
event ImportTriggerEventHandler<T> Import;
}
public abstract class OnImportBase<T> : IImportTrigger<T> where T: EventArgs
{
public event ImportTriggerEventHandler<T> Import;
protected virtual void OnImport(object sender, T args)
{
var tmp = Import;
if (tmp != null)
{
tmp(this, args);
}
}
}
public class FileSystemImportTrigger : OnImportBase<FileSystemEventArgs>
{
protected override void OnImport(object sender, FileSystemEventArgs args)
{
DoSomeExtraStuffBeforeImport();
base.OnImport(sender, args);
}
private void DoSomeExtraStuffBeforeImport()
{
}
}
public class TimerImportTrigger : OnImportBase<ElapsedEventArgs>
{
protected override void OnImport(object sender, ElapsedEventArgs args)
{
base.OnImport(sender, args);
DoSomeExtraStuffAfterImport();
}
private void DoSomeExtraStuffAfterImport()
{
}
}
This gets rid of the event subscription and instead handles it as a override (Which is the normal pattern in .NET events).

Related

C# EventHandler returns null when called

I have a two classes.In one class i am creating and raising an event as follows :
CustomerAdd Class
public class CustomerAdd
{
public delegate void Done(object Sender, EventArgs e);
public event Done ListUpdated;
public void UpdateNewList()
{
//adding items to a generic List<T>,code removed as not relevant to post
//and raising the event afterwards
if (ListUpdated != null)
{
ListUpdated(this, EventArgs.Empty);
}
}
}
MyWindow Class
public class MyWindow
{
private void SaveToDisk()
{
CustomerAdd cuss = new CustomerAdd();
cuss.ListUpdated += new CustomerAdd.Done(DisplayDetails);
cuss.UpdateNewList();
}
private void DisplayDetails()
{
//other codes here
}
}
Now, when i call the SaveToDisk method from MyWIndow class,(as i am subscribing DisplayDetails method to the ListUpDated event) , DisplayDetails is not called. The debugger shows that ListUpdated is null. I have searched for hours and failed to come up with a solution.I followed this link but still ListUpdated is null. Any guidance/help would be highly appreciated.
It works:
using System;
namespace ConsoleApp2
{
class Program
{
public class CustomerAdd1
{
public delegate void Done(object Sender, EventArgs e);
public event Done ListUpdated;
public void UpdateNewList()
{
//adding items to a generic List<T>,code removed as not relevant to post
//and raising the event afterwards
if (ListUpdated != null)
{
ListUpdated(this, EventArgs.Empty);
}
}
}
public class CustomerAdd
{
public void SaveToDisk()
{
CustomerAdd1 cuss = new CustomerAdd1();
cuss.ListUpdated += new CustomerAdd1.Done(DisplayDetails);
cuss.UpdateNewList();
}
private void DisplayDetails(object Sender, EventArgs e)
{
Console.WriteLine("Test");
}
}
static void Main(string[] args)
{
var c = new CustomerAdd();
c.SaveToDisk();
Console.ReadLine();
}
}
}
Try this:
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
CustomerReceive cr = new CustomerReceive();
cr.SaveToDisk();
}
}
public class CustomerAdd
{
public delegate void Done(object Sender, EventArgs e);
public event Done ListUpdated;
public void UpdateNewList()
{
//adding items to a generic List<T>,code removed as not relevant to post
//and raising the event afterwards
if (ListUpdated != null)
{
ListUpdated.Invoke(this, EventArgs.Empty);
}
}
}
public class CustomerReceive
{
public void SaveToDisk()
{
CustomerAdd cuss = new CustomerAdd();
cuss.ListUpdated += new CustomerAdd.Done(DisplayDetails);
cuss.UpdateNewList();
}
private void DisplayDetails(object Sender, EventArgs e)
{
int k = 0;
}
}
}
You need to do a good read on delegates and events because this is not working when there are more listeners

access backgroundworker from a public method

So i have form1 which has backgroundworker (dragged and dropped via design view).
I can get it to work in the places i need, however I need to call it from a public method.
In this public method
Utility.initpacks(object sender, EventArgs e,string formname)
SO my DoWork is in Form1.
I the public utility within the form do do a bunch of things, then THAT function needs to use the background worker inside Form1 again!
I could just copy the public method and put in the place of the method reference and all is well.. but that defeats the purpose of a public method doesn't it!?
Any ideas would be great thanks :)
EDIT:
SO my current setup (without a bunch of stuff not important):
public partial class frmimportinstitutions : Form
{
private void btnNext_Click(object sender, EventArgs e)
{
Utility.initpacks(sender, e, this.FindForm().Name);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//Do stuff
}
}
public static class Utility
{
public static void initpacks(object sender, EventArgs e,string formname)
{
//I WANT TO USE THE BACKGROUND WORKER HERE
//Do a public method
//I want to stop the background worker here
}
}
Update (basd on comments):
Michael comments just mentioned to put the background worker starting in a public method:
public void startplash(string starttext)
{
if (!backgroundWorker1.IsBusy)
{
splashtext = starttext;
backgroundWorker1.RunWorkerAsync();
}
}
Now i want to call this method from the other method. In order to do this, the other method (init packs) needs to know where this method is doesnt it.
EG.
form1.startsplash("hello world")
So now i just need to send Form1 info to init packs...
Would this be ok:
Initpacks(Form Owner)
{
Owner.startsplash("hello world")
}
Another update!
Thanks for Michael we so far have this:
public static class Utility
{
public static void RunWorkerOfForm1()
{
var target = (Form1)Application.OpenForms.OfType<Form1>().FirstOrDefault();
target?.RunWorker();
}
}
Now I need to get this to work with different forms.. I havent tried the below but this is what i am going to try next.. correct me if i am wrong:
public static class Utility
{
public static void RunWorkerOfForm1(Form owner)
{
var target = (owner)Application.OpenForms.OfType<owner>().FirstOrDefault();
target?.RunWorker();
}
}
Final Answer (as per the ticked answer) - but using my code:
public partial class frmholidaypacks : Form, IWorker
{
private void btnextrapacks_Click(object sender, EventArgs e)
{
Utility.futurepacks<frmholidaypacks>(sender, e, pxid);
}
}
public interface IWorker
{
void startplash(string starttext);
}
public void startplash(string starttext)
{
if (!backgroundWorker1.IsBusy)
{
splashtext = starttext;
backgroundWorker1.RunWorkerAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//Doing work. Using Splashtext string.
}
public static void futurepacks<T>(object sender, EventArgs e, int pxid) where T : IWorker
{
var target = (T)Application.OpenForms.OfType<T>().FirstOrDefault();
target?.startplash("Creating future packs");
}
100% Credit goes to Michael for working on this with me!
EDITED!
Ok, now I got it. Every form you want to run have to implement the IWorker interface. Then you pass the concrete Form to use as a generic parameter to the RunWorker function. The where clause only allows implementations of the IWorker interface - its (currently) not restricted to Form-instances.
public partial class Form2 : Form, IWorker
{
public Form2()
{
InitializeComponent();
}
public void RunWorker()
{
if (backgroundWorker1.IsBusy) return;
backgroundWorker1.RunWorkerAsync();
}
private void button1_Click(object sender, EventArgs e)
{
UtilityX.RunWorker<Form2>();
}
}
public static class UtilityX
{
public static void RunWorker<T>() where T : IWorker
{
var target = (T)Application.OpenForms.OfType<T>().FirstOrDefault();
target?.RunWorker();
}
}
public interface IWorker
{
void RunWorker();
}
Have you tried to use a static method,
you can put your code in a static method if you want too use it from multiple places.
public class MyClass
{
public static void MyMethod()
{
//..
}
}

Create EventHandler and listen to Event from another class

I've created event as bellow and want to listen to it and execute method in another class when it fires
but saveEvent always comes to be null and it doesn't fire
I don't know what I've missed
here's my first class has button
internal partial class OpenSaveReportWizardForm : Form
{
public event EventHandler saveEvent;
private void saveButton_Click(object sender, EventArgs e)
{
saveEvent?.Invoke(this, e);
}
}
and here's the second class where I want to listen to saveEvent
internal class Database
{
public Database()
{
Program._wizardForm.saveEvent += (sender, e) => HandleSaveMethod( );
}
public void HandleSaveMethod()
{
// do something
}
here's where I open the form
internal class Program
{
public static OpenSaveReportWizardForm _wizardForm;
private static void Main()
{
OpenFileCommandHandler();
}
void OpenFileCommandHandler()
{
_wizardForm = new OpenSaveReportWizardForm( );
_wizardForm.ShowDialog();
}
}
Because you disposed wizardForm, after that event is cleared.
You should write next code:
internal class Database
{
private bool _isDisposed;
private OpenSaveReportWizardForm _wizardForm;
public Database()
{
_wizardForm = new OpenSaveReportWizardForm(m_Opening,m_ConnectionProperties,m_ColumnProperties))
_wizardForm.saveEvent += (sender, e) => HandleSaveMethod( );
}
public void HandleSaveMethod()
{
// do something
}
public void Dispose()
{
if(_isDisposed)
return;
_isDisposed = true;
_wizardForm.saveEvent -= HandleSaveMethod;
_wizardForm.Dispose();
}

How can I pass a string from an onclick event to another class

I am new to encapsulation. I would like to pass a string from the OnClick event of one of my buttons to a private method in another class. When I try to set the string from inside the method now it says that variable isn't recognized How can I safely pass it in?
My OnClick Event:
public partial class ClassOne
protected void lnkbtnKeySearch_Click(object sender, EventArgs e)
{
string myNewString = "Change the value of the string";
}
Class I want to pass it into:
public partial class ClassTwo
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
LoadData();
}
}
private void LoadData()
{
{
string filterText = myNewString;
}
}
You can try to hold the value of the string inside a session variable:
protected void lnkbtnKeySearch_Click(object sender, EventArgs e)
{
Session["myNewString"] = "Change the value of the string";
}
private void LoadData()
{
string filterText = Session["myNewString"].ToString();
}
One way is to make MyNewString static.
public partial class ClassOne
{
public static string MyNewString{set;get;}
protected void lnkbtnKeySearch_Click(object sender, EventArgs e)
{
MyNewString= "Change the value of the string";
}
}
Then
public partial class ClassTwo
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
LoadData();
}
}
private void LoadData()
{
{
string filterText = ClassOne.MyNewString;
}
}
}

How to access the event handler in another class

I have a click event handler in class A with some logic. And now i want to access class A event handler from class B and do some logic so that class B event hadler logic fires first followed by class A event handler.
Example:
Class A
private void calculate_Click(object sender, System.EventArgs e)
{ this.MyMethod(); }
Class B
private void calculate_Click(object sender, System.EventArgs e)
{ // My new code.. (This should trigger first) this.MyMethod(); }
You may use event exposed by class A and consumed by class B like we do with Button class. Button exposes click event and in our form class we subscribe for click event being exposed by Button class.
I found this simple example for understanding here
using System;
namespace wildert
{
public class Metronome
{
public event TickHandler Tick;
public EventArgs e = null;
public delegate void TickHandler(Metronome m, EventArgs e);
public void Start()
{
// while (true) //uncomment this line if you want event to fire repeatedly
{
System.Threading.Thread.Sleep(3000);
if (Tick != null)
{
Tick(this, e);
}
}
}
}
public class Listener
{
public void Subscribe(Metronome m)
{
m.Tick += new Metronome.TickHandler(HeardIt);
}
private void HeardIt(Metronome m, EventArgs e)
{
System.Console.WriteLine("HEARD IT");
}
}
class Test
{
static void Main()
{
Metronome m = new Metronome();
Listener l = new Listener();
l.Subscribe(m);
m.Start();
}
}
}
Assuming class B has instance member A instanceOfClassA initilized properly with an instance of A:
private void calculate_Click(object sender, System.EventArgs e)
{
// My new code.. (This should trigger first)
instanceOfClassA.MyMethod();
// other code
}
You may also consider inheriting class B from A:
class B:A
{
private void calculate_Click(object sender, System.EventArgs e)
{
// My new code.. (This should trigger first)
this.MyMethod(); // will come from base class A implementation.
// other code
}
}

Categories