Should I go with reflection or delegates (C#)? - c#

In one of my previous questions I explained about a form class that contain form field objects to save data in a user profile object (using profile provider).
The code is here bellow. Basically what I would like to accomplish is to pass as a parameter to my form field objects the field of the Profile object that they should interact in order to save the data later on.
You can see that in the following line:
//LastNameFormLine is an control that was added to my form page.
//The ProfileField parameter stores the field of the UserProfile object that is being manipulated by this control
LastNameFormLine.ProfileField = "UserProfile.LastName";
I was reading about reflection to be able to save this value in the UserProfileVisitor class, but I came across this concept of delegate in C# which I am not sure yet if I fully grasp.
Is it possible to delegate the ProfileField to a property on my UserProfile class? Or should I forget about it and go with reflection?
What would you suggest?
public partial class UserProfileForm : CustomIntranetWebappUserControl
{
protected override void OnInit(EventArgs e)
{
//AutoEventWireup is set to false
Load += Page_Load;
CancelLinkButton.Click += CancelButtonClickEvent;
SaveLinkButton.Click += SaveButtonClickEvent;
base.OnInit(e);
}
private void SaveButtonClickEvent(object sender, EventArgs e)
{
VisitFormFields();
}
private void VisitFormFields()
{
var userProfileVisitor = new UserProfileVisitor();
foreach (var control in Controls)
{
if (control is FormFieldUserControl)
{
var formField = (FormFieldUserControl) control;
formField.Visit(userProfileVisitor);
}
}
userProfileVisitor.Save();
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindText();
}
}
private void BindText()
{
LastNameFormLine.LabelText = string.Format("{0}:", HomePage.Localize("Last Name"));
LastNameFormLine.InputValue = UserProfile.LastName;
LastNameFormLine.IsMandatoryField = true;
LastNameFormLine.IsMultilineField = false;
LastNameFormLine.ProfileField = "UserProfile.LastName";
//... the rest of this method is exactly like the 4 lines above.
}
}
public abstract class FormFieldUserControl : CustomIntranetWebappUserControl
{
public string ProfileField { get; set; }
public abstract void Visit(UserProfileVisitor userProfileVisitor);
}
public partial class FormLineTextBox : FormFieldUserControl
{
//... irrelevant code removed...
public override void Visit(UserProfileVisitor userProfileVisitor)
{
if (userProfileVisitor == null)
{
Log.Error("UserProfileVisitor not defined for the field: " + ProfileField);
return;
}
userProfileVisitor.Visit(this);
}
}
public class UserProfileVisitor
{
public void Visit(FormLineTextBox formLine)
{
// The value of formLine.ProfileField is null!!!
Log.Debug(string.Format("Saving form field type {1} with profile field [{0}] and value {2}", formLine.ProfileField, formLine.GetType().Name, formLine.InputValue));
}
// ... removing irrelevant code...
public void Save()
{
Log.Debug("Triggering the save operation...");
}
}

Delegates are not for properties. However, Reflection is slow, may have issues with code security and it's not typesafe and may lead to runtime instead of compile-time problems on naming errors due to the late-bound nature.
That said, you may want to use getter and/or setter methods and use delegates on those.

Related

how to access array from one class to another class in C# winforms?

I want to access array from one class to another class because my end-user enter the name list on one class. That list store into array in the same class. Then that name list access from another class. I'm not getting any errors in compile time. only I'm getting a run time error. I'm literally sorry to all coz I'm absolutely noob :(
public partial class custom : Form //class one which is end user enter the name list
{
public string PresentValue;
public string NormalValue;
public string[] PValue = new string[50];//public array
public string[] NValue = new string[50];//public array
}
public static int PresentArray = 0;// this line is used to increment the array index
private void cstmsvbtn_Click(object sender, EventArgs e)//this line enter the user namelist
{
PresentValue = cstmtst1.Text + "_PV";//concatinate '_PV'
NormalValue = cstmtst1.Text + "_NV";//concatinate '_NV'
PValue[PresentArray] = PresentValue;
NValue[PresentArray] = NormalValue;
PresentArray++;
}
public partial class print : Form // class to which is end user want to access that name list
{
custom customarray = new custom();// I instantiate the custom cass object
private void button1_Click(object sender, EventArgs e)//when i press this button message box show an empty white box only
{
MessageBox.Show(CustomArray.PValue[0],CustomArray.NValue[0]);
}
}
This is a common requirement and there are many ways to achieve this outcome (some of which might be considered "hacky"). Things I don't recommend:
Changing visibility to public for data fields that should be private
Creating tight dependencies of one form to the implementation details of another.
Creating "global" variables using the static keyword.
Since you claim to be a "noob" I'd like to suggest learning about the event keyword and using Events to communicate between forms. Yes, there is a small learning curve here, but chances are you'll use this a lot and it will be a good investment. I put a link in the Comments section so you can clone or browse this example and see if it does what you want it to (I recommend setting debugger break points so you can see why it does what it does).
What you have (according to your post) is a print form and a custom form. And though you don't really say, this example will have a MainForm that can show the other two:
PrintForm
The PrintForm requires the NValue and PValue arrays to do its printing. By declaring an event named ArrayRequest we give it the ability to request these arrays. Importantly, this class doesn't need to have any knowledge of where this information might be coming from.
public partial class PrintForm : Form
{
public PrintForm() => InitializeComponent();
This is how the class can initiate the request
public event ArrayRequestEventHandler ArrayRequest;
protected virtual void OnArrayRequest(ArrayRequestEventArgs e)
{
ArrayRequest?.Invoke(this, e);
}
When the button is clicked, try and get the information by callingOnArrayRequest
private void buttonShowArray_Click(object sender, EventArgs e)
{
ArrayRequestEventArgs req = new ArrayRequestEventArgs();
OnArrayRequest(req);
if(req.Count == 0)
{
MessageBox.Show("Invalid Request");
}
else
{
String[] allValues =
Enumerable.Range(0, req.Count)
.Select(index => $"{req.NValue[index]} | {req.PValue[index]}")
.ToArray();
MessageBox.Show(
text: string.Join(Environment.NewLine, allValues),
caption: "All Values"
);
}
}
}
// Defined outside the PrintForm class
public delegate void ArrayRequestEventHandler(Object sender, ArrayRequestEventArgs e);
public class ArrayRequestEventArgs : EventArgs
{
public int Count { get; set; }
public string[] PValue { get; set; }
public string[] NValue { get; set; }
}
CustomForm
The CustomForm as shown in your post is the class that contains the arrays.
public partial class CustomForm : Form
{
public CustomForm()
{
InitializeComponent();
}
We give this class the ability to fulfill a request for the arrays.
internal void ArraysRequested(object sender, ArrayRequestEventArgs e)
{
e.Count = _presentArray;
e.NValue = _nValue;
e.PValue = _pValue;
}
The data held in this class should be private.
// These should all be private
// See naming conventions: https://stackoverflow.com/a/17937309/5438626
// Set up visual studio to do this automatically: https://ardalis.com/configure-visual-studio-to-name-private-fields-with-underscore/
private string _normalValue;
private string _presentValue;
private int _presentArray = 0;
private string[] _pValue = new string[50];//public array
private string[] _nValue = new string[50];//public array
private void cstmsvbtn_Click(object sender, EventArgs e)
{
_presentValue = $"{cstmtst1.Text}_PV"; //concatinate '_PV'
_normalValue = $"{cstmtst1.Text}_NV"; //concatinate '_NV'
// Make sure index doesn't exceed the size of the array
if ((_presentArray < _pValue.Length) && (_presentArray < _nValue.Length))
{
_pValue[_presentArray] = _presentValue;
_nValue[_presentArray] = _normalValue;
_presentArray++;
}
else MessageBox.Show("Array is Full");
Text = $"Custom: Count={_presentArray}";
cstmtst1.Text = $"Hello {_presentArray + 1}";
}
}
MainForm
It is the MainForm class that oversees the operations and "knows" how the forms should interact. The constuctor method is where the connection is made between the event fired by PrintForm and the fulfillment by the CustomForm.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
// THIS IS THE "GLUE"
_printForm.ArrayRequest += _customForm.ArraysRequested;
}
private CustomForm _customForm = new CustomForm();
private PrintForm _printForm = new PrintForm();
// In MainForm.Designer.cs
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
_customForm.Dispose();
_printForm.Dispose();
}
base.Dispose(disposing);
}
private void buttonShowCustom_Click(object sender, EventArgs e)
{
_customForm.ShowDialog(owner: this);
}
private void buttonShowPrint_Click(object sender, EventArgs e)
{
_printForm.ShowDialog(owner: this);
}
}
You will need to adapt this to your specific requirements but hopefully this will give you some basics to go on.

C# - Casting EventArgs to a generic Event type

I'm working on a game just for fun, I'm attempting to implement events but I get the error:
Error CS0305: Using the generic type 'Game.NPC.Events.MurderEventArgs' requires 2 type arguments (CS0305) (Game)
The code which is raising the event:
public event EventHandler<EventArgs> OnMurderEvent;
public void RaiseMurderEvent<TVictim, TMurderer>(TVictim npcVictim, TMurderer npcMurderer)
{
if (OnMurderEvent != null)
{
OnMurderEvent(this, new MurderEventArgs<TVictim, TMurderer>(npcVictim, npcMurderer));
}
}
My code which is handling the event:
npc.OnMurderEvent += HandleMurderEvent;
npc.RaiseMurderEvent<Victim, Murderer>(null, null);
static void HandleMurderEvent(object sender, EventArgs e)
{
Console.WriteLine((MurderEventArgs)e);
}
The casting won't work because it doesn't have the generic types, can anybody help?
Suppose your MurderEventArgs looks like that:
class MurderEventArgs<TVictim, TMurderer> : EventArgs
{
public MurderEventArgs(TVictim victim, TMurderer murderer)
{
//your code here
}
}
You may change your EventHandler to:
class EventRaiser<TVictim, TMurderer>
{
public event EventHandler<MurderEventArgs<TVictim, TMurderer>> OnMurderEvent;
public void RaiseMurderEvent(TVictim npcVictim, TMurderer npcMurderer)
{
if (OnMurderEvent != null)
{
OnMurderEvent(this, new MurderEventArgs<TVictim, TMurderer>(npcVictim, npcMurderer));
}
}
}
And your usage would look like this:
static void FireAndForget<TVictim, TMurderer>(TVictim victim, TMurderer murderer)
{
var npc = new EventRaiser<TVictim, TMurderer>();
npc.OnMurderEvent += HandleMurderEvent<TVictim,TMurderer>;
npc.OnMurderEvent += HandleMurderEventAlt;
npc.RaiseMurderEvent(victim, murderer);
}
static void HandleMurderEvent<TVictim, TMurderer>(object sender, EventArgs e)
{
var murderEvent = (MurderEventArgs<TVictim, TMurderer>)e;
//do something with the event
}
//or alternatively
static void HandleMurderEventAlt<TVictim, TMurderer>(object sender, MurderEventArgs<TVictim, TMurderer> e)
{
//do something with the event
}
some afterthoughts
I named it FireAndForget for a reason. You need create a new EventRaiser instance for each type combination of TVictim and TMurderer or re-use an already existing instance for a combination. But that defeats the purpose of an EventListener in my opinion.
You can alternatively create a public void Murder<TVictim, TMurderer>(TVictim victim, TMurderer murderer) method and do your stuff there. No need for an event at all. Drawback: a TMurderer needs to be forced to call that method.
Or implement MurdererBy<TMurderer>(TMurderer murderer) at any TVictim class and raise a MurderedByEvent. Drawback: a TVictim "knows" the TMurderer. Let TMurderer implement a Murders<TVictim>(TVictim victim) method but who enforces that the murder is registered?
Or create some kind of LifeCycleManager, TVictim's can only be created by that Manager (Factory pattern). On each instance creation the Manager registers its own EventListener to the MurderedByEvent of the new TVictim instance.
summary
My sample code from above answers your question to some extend but you should re-think your model. Who needs to know of the event? Should every murder be known? What happens to the victim?
What is required to be known for a murder event? Name of the victim and murderer only then go with an interface model proposed by the other authors.
With interfaces you can get a workaround.
If your classes: Doctor, Student, Teacher, etc... implement or inherit from the same interface/class, you could write:
public event EventHandler<MurderEventArgs> OnMurderEvent;
public void RaiseMurderEvent(IPerson npcVictim, IPerson npcMurderer)
{
if (OnMurderEvent != null)
{
OnMurderEvent(this, new MurderEventArgs(npcVictim, npcMurderer));
}
}
and do that:
npc.OnMurderEvent += HandleMurderEvent;
npc.RaiseMurderEvent(null, null);
static void HandleMurderEvent(object sender, MurderEventArgs e)
{
Console.WriteLine(e);
}
For your class hierarchy, you could have something like that:
public interface IPerson
{
// All the methods/properties that make a person
}
public abstract class Person : IPerson
{
// All the common implementations
}
public class Doctor/Student/Teacher/etc... : Person
{
// All the specific implementations
}
I'm not completely sure without seeing more of your code, but since it seems like the type params TVictim, TMurderer of the second code snippet correspond to the concrete types Victim, Murderer then perhaps your second snippet just needs to be changed to the following:
npc.OnMurderEvent += HandleMurderEvent;
npc.RaiseMurderEvent<Victim, Murderer>(null, null);
static void HandleMurderEvent(object sender, EventArgs e)
{
Console.WriteLine((MurderEventArgs<Victim, Murderer>)e);
}
In reply to your comment, to remove the generics (which seems to be what most people are recommending here), you would change MurderEventArgs to something like:
public class MurderEventArgs : EventArgs
{
public MurderEventArgs(IPerson victim, IPerson murderer)
{
Victim = victim;
Murderer = murderer;
}
public IPerson Victim { get; }
public IPerson Murderer { get; }
}
Where the events are raised, it would be something like:
public event EventHandler<MurderEventArgs> OnMurderEvent;
public void RaiseMurderEvent(IPerson npcVictim, IPerson npcMurderer)
{
if (OnMurderEvent != null)
OnMurderEvent(this, new MurderEventArgs(npcVictim, npcMurderer));
}
There would be some common information in the IPerson interface and then more in the specialised implementations -
public interface IPerson
{
string Name { get; }
}
public abstract class Person : IPerson
{
protected Person (string name)
{
Name = name;
}
public string Name { get; }
}
public class Adult : Person
{
public Adult(string name) : base(name) { }
}
public class Child : Person
{
public Child(string name, int age) : base(name)
{
Age = age
}
public int Age { get; }
}
Finally, the event listening code will be something along these lines:
npc.OnMurderEvent += Npc_OnMurderEvent;
npc.RaiseMurderEvent(victim, murderer);
private static void Npc_OnMurderEvent(object sender, MurderEventArgs e)
{
Console.WriteLine(e.Victim.Name + " was murdered by " + e.Murderer.Name);
var murdererAsChild = e.Murderer as Child;
if (murdererAsChild != null)
Console.WriteLine(".. who is only a child, it must be Damien!");
// Deal with any other special cases for particular Victim, Murderer combinations..
}
As you asked in a comment on the answer by #romain-aga (who I shamelessly stole the IPerson interface name from!), it would be necessary to perform runtime casting if you were interested in murderers or victims that were of more specific types.

Having two variables mirror each other

class InputBox : TextBox
{
private object value;
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
try
{
value = TypeDescriptor.GetConverter(value.GetType()).ConvertFromString(Text);
}
catch (Exception e1)
{
Utils.offensiveMessage("Invalid format! This field accepts only " + value.GetType().Name + "!");
Focus();
Text = value.ToString();
}
}
public InputBox(object value) : base()
{
this.value = value;
initializeComponent();
}
private void initializeComponent()
{
Text = value.ToString();
}
}
This class here takes an object as an input, represents it as a string and then makes sure that whatever is written in the textbox stays of the same type. It has it's limitation as to what types it can handle (value-types only), but I'm working within those confines. Why do I need this?
I have a large class that has a lot of subclasses that all in the end contain value-types. What I want is to present that data to the user and enable him to edit it. This is kind of an atom of my UI.
What would be great here is a way for a variable that I have in my large class to be mirrored by the variable in the above displayed class. When I change one in the little class, the one in the big class is changed in the same manner.
How can I do it?
I would look at using Winforms Databinding - it does this already with two way notification, if you support INotifyPropertyChanged on the large class.
However, if you don't want to use databinding for some reason, you can consider this as an alternative to just make your code work:
public static class Program
{
public static void Main()
{
var largeClass = new LargeClass() { Blah = 423 };
var inputBox = new InputBox<double>(
() => largeClass.Blah,
(a) => largeClass.Blah = a
);
}
}
public class LargeClass
{
public double Blah { get; set; }
}
public class InputBox<T> : TextBox
{
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
try
{
_setter((T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(Text));
}
catch (Exception e1)
{
//Utils.offensiveMessage("Invalid format! This field accepts only " + value.GetType().Name + "!");
Focus();
Text = _getter().ToString();
}
}
private Func<T> _getter;
private Action<T> _setter;
public InputBox(Func<T> getter, Action<T> setter) : base()
{
_getter = getter;
_setter = setter;
InitializeComponent();
}
private void InitializeComponent()
{
Text = _getter().ToString();
}
}
I ended up creating a wrapper that implemented an observer-pattern type interface.
Wrapping the object made sure that even value-types were passed as references (since I was now passing the wrapper instead of the value).
I setup the observer pattern so that the observed wrapped object could notify any observers which would then update accordingly.

Update label through another class

I'm trying to call method Run in script1. Then from script1 call method alert or wait and try to update statusLabel in Form1. But this code has an error.
static Label status = this.Controls.Find("statusLabel", true).FirstOrDefault() as Label;
This code will only work in Form1. Because this return error in another classes. Maybe it is not correct and you know better solution.
P.S. I know how to solve this problem (see below "Not the best solution"), but the code will be ~ 10-30 new lines.
Project
Form1.cs
public partial class Form1 : Form
{
private void statusLabel_Click(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
script1.Run();
...
script30.Run();
}
}
function.cs (Frequently used functions)
public class function
{
static Label statusLabel = this.Controls.Find("statusLabel", true).FirstOrDefault() as Label;
static public void alert(string str)
{
statusLabel.Text = str;
}
static public void wait(int sec)
{
int i = 0;
while (i++ < sec)
{
statusLabel.Text = "Wait: " + (sec+1-i).ToString();
Thread.Sleep(1000);
}
}
}
script1.cs (script1,script2 ... it is about 30 mini programs)
public class script1
{
static public void Run()
{
function.alert("Script1 is running");
function.wait(5);
function.alert("Script1 is done");
}
}
Not the best solution
remove in function.cs
static Label status = this.Controls.Find("statusLabel", true).FirstOrDefault() as Label;
Form1.cs
public partial class Form1 : Form
{
private void Form1_Load(object sender, EventArgs e)
{
script1.Run(this.statusLabel);
}
}
function.cs
public class function
{
private Label _statusLabel;
public scriptClass(Label statusLabel)
{
_statusLabel = statusLabel;
}
}
script1.cs (script1,script2 ... it is about 30 mini programs)
public class script1
{
static public void Run(Label statusLabel)
{
function _function = new function(statusLabel);
}
}
The statusLabel object is owned by, and should be encapsulated and hidden by, the Form1 class. To ensure good decoupling of your classes, as well as proper data hiding, only the Form1 class should be directly accessing it. And it should (by default) be able to access it via a field named statusLabel (i.e. no need to call this.Controls.Find() (nor should that even work from the function class, since that class also is not the owner of the object, nor of a Controls property).
The correct way to do this is for the script1 class to expose a StatusText property, and an event that is raised when the property value changes. There are two canonical ways to implement the event:
Implement an event named StatusTextChanged
Implement the INotifyPropertyChanged interface
Note that in your example, #2 is not an option because you are using static classes to implement your scripts. IMHO this is inadvisable for a variety of reasons, but since #1 is a perfectly fine solution I won't belabor that point. :)
The first looks like this:
class script1
{
public static string StatusText { get; private set; }
public static event EventHandler StatusTextChanged;
static public void Run()
{
ChangeStatusText("Script1 is running");
function.wait(5);
ChangeStatusText("Script1 is done");
}
static void ChangeStatusText(string text)
{
StatusText = text;
EventHandler handler = StatusTextChanged;
if (handler != null)
{
handler(null, EventArgs.Empty);
}
}
}
Then in Form1:
public partial class Form1
{
private void Form1_Load(object sender, EventArgs e)
{
script1.StatusTextChanged += (sender1, e1) => statusLabel.Text = script1.Text;
script1.Run();
...
script30.StatusTextChanged += (sender1, e1) => statusLabel.Text = script30.Text;
script30.Run();
}
}
Note in the above, each scriptX class has to reimplement the event. You could instead make a base class that each of the scriptX classes inherits, and which contains the event in question. Then the Form1 class need only subscribe to the one base class event. It would also address, or at least minimize the hassle of, the issue of leaving event handlers subscribed to 30 different events.
Of course, in this case then the Form1 class won't know which script is updating the text, but maybe that doesn't matter in your case.
Also note that if you did make the scriptX classes non-static, you might then again run into the issue of having to subscribe multiple times. But that is much more easily handled, since it seems certain in that case you'd use a base class, and so it would be easy to generalize the "subscribe, run script, unsubscribe" logic into a helper method.

ASP.NET call button action from different class

I have a class Lot with a function AddPiece(piece).
I also have a Page with a button btnPanel that on click fires the function
public void btnPanel_OnClick(object sender, EventArgs e){}
I want to call the btnPanel_OnClick from the Addpiece function but when I try to do it it does not show in the intlliSense and I get this compilation error "The name 'btnPanel_OnClick' does not exist in the current context". Both classes are in the same namespace. Is this possible?
Here is what I have:
namespace GraphicW_Array
{
public partial class Board : System.Web.UI.Page
{
public void btnPanel_OnClick(object sender, EventArgs e)
{
...code...
}
}
}
and
namespace GraphicW_Array
{
public class Lot
{
public void addPiece(int piece)
{
lotPresent[lotLoad] = piece;
lotLoad++;
}
}
}
I think the answer is yes you can but you probably don't want to. To call the method you need and instance of your page class so you could do
namespace GraphicW_Array
{
public class Lot
{
public void addPiece(int piece)
{
lotPresent[lotLoad] = piece;
lotLoad++;
var myPage = new Board();
myPage.btnPanel_OnClick(null,EventArgs.Empty);
}
}
}
But what would that actually do? I have no idea because you haven't posted the code but i suspect it won't do anything useful for you.
What are you actually trying to achieve?
Maybe this is want you want
namespace GraphicW_Array
{
public class Lot
{
public void addPiece(int piece, Board myPAge)
{
lotPresent[lotLoad] = piece;
lotLoad++;
myPage.btnPanel_OnClick(null,EventArgs.Empty);
}
}
}
Then in your page you can call it like this:
var myLot = new Lot();
myLot.addPiece(4,this);
Yes, this is possible.
Ensure your Lot class has a reference to the Board class in order to be able to call it, or define an event on it that the Board class can subscribe to and that will call this mathod when the event fires.
If you don't use the sender and e parameters, just pass a null and EventArgs.Empty.
You can call page's event by passing either null(if sender and EventArgs is not mandatory) but below is the better way to go.
It is not wise and not good practice to call a event from a class, however you can create another method with arguments in your class and then call it with desired parameters when it is needed.
This is can be accomplished as below:
Say you have below event
public void btnPanel_OnClick(object sender, EventArgs e)
{
//Do some common tasks to do here
}
Rearrange it as below:
public void btnPanel_OnClick(object sender, EventArgs e)
{
Lot lot = new Lot();
lot.CommonFunction(arg1, arg2); // Pass required data
}
public class Lot
{
public void AFunction()
{
//Do something
//...
CommonFunction(arg1, arg2); // Pass required data
//...
//Do something
}
public void CommonFunction(string arg1, string arg2)
{
// Do some common tasks to do here
}
}

Categories

Resources