Type of params in c# methods - c#

I have a method
public string GetValue(TextBox txt, DropdownList ddl)
{
if(txt != null)
return txt.text;
else
return ddl.SelectedValue;
}
I can only pass either textbox or ddl and null for the other param. How can I resolve this issue?
Dynamically i call that method and at a time either TextBox or DDl exists. So based on that i have to return value from That Control.
I am getting some error message as Method has invalid arguments when i pass Null.

I can only pass either textbox or ddl and null for the other param. How can I resolve this issue?
If this is the business rule you want to enforce, you don't resolve it, you change it. Use method overloading to only accept the type of parameter you actually want to use.
public string GetValue(TextBox tb)
public string GetValue(DropDownList ddl)

I don't think you should be doing a method like this.
From the signature you have some code like
var value = GetValue(txtMyText, null);
which sets value to txtMyText.Text;
OR you have
var value = GetValue(null, ddlMyList);
which sets value to ddl.SelectedValue;
The problem here is that this removes readability. When I'm reading your code I see that you're doing a GetValue() but I'm at a loss for why you're passing in null in some parameters.
It's actually fairly clear, when reading code to just see:
var value = txtMyTextBox.Text;
var dropDownValue = ddlMyList.SelectedValue;
Making this method isn't really all that useful, because you have to handle each and every control type. The classes already have methods to get their value, and you trying to write a utility method that will get a value regardless of class type obscures what is really going on with little value.
Further, if you add more types to this method, you'll end up doing an If/Else until you find the type, and return the value. This causes unnecessary CPU cycles, especially since you will already know the type at design time (since you're passing in null for one parameter.)

if you want to pass many parameters you can use param keyword
public string GetValue(params object[] controls)
{
foreach (var item in controls)
{
if (item != null)
{
if (item is TextBox)
return (item as TextBox).Text;
if (item is CheckBox)
return (item as CheckBox).Checked.ToString();
if (item is DropDownList)
return (item as DropDownList).SelectedValue.ToString();
}
}
return string.Empty;
}
and call the method as this
GetValue(TextBox1);
GetValue(DropDownList1);
GetValue(CheckBox1);
GetValue(null,DropDownList1);
GetValue(TextBox1,DropDownList1);
GetValue(TextBox1,DropDownList1,CheckBox1);

Every Control inherits the class control. So one parameter is enough. You just have to determine the type:
public string GetValue(Control ctl) {
if (ctl != null) {
//Textbox
if (ctl is TextBox) return ctl.Text;
//Combobox
if (ctl is ComboBox) {
ComboBox cb = ctl as ComboBox;
return cb.SelectedText;
}
//...
}
//Default Value (You can also throw an exception
return "";
}

Related

How to iterate through the keys of a dictionary via ComboBox.Items

My application has the following method, which previously worked, but after refactoring into multiple assemblies no longer works.
/// <summary>
/// Attempt to set the combobox to the item with the supplied string
/// </summary>
protected void SetComboBoxSelection(ComboBox cmb, string ItemText)
{
if (cmb.DisplayMember.ToLower() == "key" && cmb.ValueMember.ToLower() == "value")
{
foreach (KeyValuePair<string, object> item in cmb.Items)
{
if (item.Key == ItemText || ((string)item.Key).HasPatternMatch(ItemText))
{
cmb.SelectedItem = item;
return;
}
}
}
}
I get the following exception:
System.InvalidCastException: 'Unable to cast object of type 'System.Collections.Generic.KeyValuePair`2[System.String,NetworkSyncObjects.NetworkFolder]' to type 'System.Collections.Generic.KeyValuePair`2[System.String,System.Object]'.'
This method is being used to set the selection of various comboboxes, each with a different dictionary type, but all of which has the 'key' as a string type. Now, my previous question was closed due to the very unhelpful answer of (paraphrasing) "thats not allowed. heres an duplicate question that only sort of explains why".
Once again, this code WORKED. But now it doesn't. I understand that it its apparently not working due to the supposed invalid casting of the items within the list. (The duplicate answer was about casting the list itself, not the items within the list).
Here is what I've tried
using dynamic item -> doesn't compile.
using KeyValuePair<string,dynamic> -> compiles but gets an identical runtime exception as noted above
using the var obj as dynamic / var obj as KeyValuePair<string,object> -> both always result in a null obj
Question:
How can I modify this method to work with any combobox whose itemsource is a Dictionary<string,T> where T is various types of objects)?
For example, some will be: Dictionary<string,NetworkFolder>, Dictionary<string,FileInfo>, Dictionary<string,DirectoryInfo>, Dictionary<string,int>
As far as I can tell, there is no way to access the Key portion of the KeyValuePair within the list unless you cast the list, which is apparently not allowed without explicitly calling out the exact type in the keyvaluepair. Once again, no idea why this was working and now isnt. But here I am.
Updated Method, using the IDictionary interface and directly accessing the datasource binding was how I resolved it.
Modified from this post: https://stackoverflow.com/a/55850559/12135042
protected void SetComboBoxSelection(ComboBox cmb, string ItemText)
{
if (cmb.DisplayMember.ToLower() == "key" && cmb.ValueMember.ToLower() == "value")
{
IDictionary Items = (IDictionary)((BindingSource)cmb.DataSource).DataSource;
int i = 0;
foreach (string item in Items.Keys)
{
if (item == ItemText || item.HasPatternMatch(ItemText))
{
cmb.SelectedIndex = i;
return;
}
i++;
}
}
}

Replacement of switch statement - getting properties via a string name

Basically, i have a system in place where my datagrid marks cells that have changed with a new background colour, to do this i have a method in the object that contains these properties that receives a string which is the name of the property to check, and then a switch statement that takes that string to check the correct property.
public Color HasChanged(string value)
{
switch (value)
{
case "CILRef":
if (_localShipment.cilRef != _originalShipment.cilRef)
{
return Colors.SkyBlue;
}
else
{
return Colors.White;
}
case "ArrivedAtPortDate":
if (_localShipment.arrivedAtPortDate != _originalShipment.arrivedAtPortDate)
{
return Colors.SkyBlue;
}
else
{
return Colors.White;
}
}
}
I've removed the rest of the properties for brevity.
Now i get the nagging sensation that there is a cleaner way to do this string>property without using a switch statement, but i can't for the life of me find anything on google, it's hard to search without some keyword to go on.
I'm also attempting to only save those properties that have changed, i was going to place any changed property name into an array, and then have a loop with yet another switch statement that checked that array and then saved the correct property. However this again seems untidy to me.
is there a cleaner solution to this, hopefully that could handle the addition of new properties without needing to add new code to the switch statements.
I can include the rest of the code that does this checking (namely the WPF binding on the datagrid, and a converter that calls the checking method with the property name as a string parameter) if needed.
You can write an extension method like:
public static object GetPropValue(this object o, string propName)
{
return o.GetType().GetProperty(propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase)
.GetValue(o);
}
and use it
if(localShipment.GetPropValue(value).Equals(originalShipment.GetPropValue(value)))
{
}
You could define a common interface to your properties, then create a dictionary of properties like so:
var properties = new Dictionary<string, IProperty>();
and access them like this:
properties["CILRef"]
I would say a switch statement is fine, however, you could do the body of the case in single lines using the condition operator
switch (value)
{
case "CILRef":
return _localShipment.cilRef != _originalShipment.cilRef ? Colors.SkyBlue : Colors.White;
case "ArrivedAtPortDate":
return _localShipment.arrivatedAtPortDate != _originalShipment.arrivedAtPortDate ? Colors.SkyBlue : Colors.White;
...
}
But you would still have repetitive code here, you could take this a step further and have a GetColor method which took a function in
public Colors GetColor(Func<bool> condition)
{
return condition() ? Colors.SkyBlue : Colors.White;
}
...
switch (value)
{
case "CILRef":
return GetColor(() => _localShipment.cilRef != _originalShipment.cilRef);
case "ArrivedAtPortDate":
return GetColor(() => _localShipment.arrivatedAtPortDate != _originalShipment.arrivedAtPortDate);
}
Looking at your code closer, it does appear you are comparing the same property on each shipping and you could literally reduce this to one check using Reflection
public Color HasChanged(string value)
{
var date1 = _localShipment.GetType()
.GetProperty(value)
.GetValue(_localShipment, null);
var date2 = _originalShipment.GetType()
.GetProperty(value)
.GetValue(_originalShipment, null);
return date1 != date2 ? Colors.SkyBlue : Colors.White;
}
For brevity, you could create an extension method to wrap up the Reflection part
public static T Get<T>(this object obj, string name)
{
return obj.GetType().GetProperty(name).GetValue(obj, null);
}
...
return _localShipment.Get<DateTime>(value) != _originalShipment.Get<DateTime>(value) ? Colors.SkyBlue : Colors.White;

How to safely check if a dynamic object has a field or not

I'm looping through a property on a dynamic object looking for a field, except I can't figure out how to safely evaluate if it exists or not without throwing an exception.
foreach (dynamic item in routes_list["mychoices"])
{
// these fields may or may not exist
int strProductId = item["selectedProductId"];
string strProductId = item["selectedProductCode"];
}
using reflection is better than try-catch, so this is the function i use :
public static bool doesPropertyExist(dynamic obj, string property)
{
return ((Type)obj.GetType()).GetProperties().Where(p => p.Name.Equals(property)).Any();
}
then..
if (doesPropertyExist(myDynamicObject, "myProperty")){
// ...
}
This is gonna be simple. Set a condition which checks the value is null or empty. If the value is present, then assign the value to the respective datatype.
foreach (dynamic item in routes_list["mychoices"])
{
// these fields may or may not exist
if (item["selectedProductId"] != "")
{
int strProductId = item["selectedProductId"];
}
if (item["selectedProductCode"] != null && item["selectedProductCode"] != "")
{
string strProductId = item["selectedProductCode"];
}
}
You need to surround your dynamic variable with a try catch, nothing else is the better way in makking it safe.
try
{
dynamic testData = ReturnDynamic();
var name = testData.Name;
// do more stuff
}
catch (RuntimeBinderException)
{
// MyProperty doesn't exist
}

Passing properties as parameters to be Got and Set

Well, I need to repeat same code for many properties.
I've seen examples taking Action delegates, but they don't fit quite well here.
I want something like this: (see explanation below)
Dictionary<Property, object> PropertyCorrectValues;
public bool CheckValue(Property P) { return P.Value == PropertyCorrectValues[P]; }
public void DoCorrection(Property P) { P.Value = PropertyCorrectValues[P]; }
.
I want to have a dictionary containing many properties and their respective "correct" values. (I know it's not well declared, but that's the idea). Properties are not necessarely inside my class, some of them are in objects of different assemblies.
A method bool CheckValue(Property). This method must access the actual value of the property and compare to the correct value.
And a method a void DoCorrection(Property). This one sets the property value to the correct value.
Remember I have many of those properties, I wouldn't like to call the methods by hand for each property. I'd rather iterate through the dicionary in a foreach statement.
So, the main question is in the title.
I've tried the by ref, but properties don't accept that.
Am I obligated to use reflection??? Or is there another option (if I need, reflection answer will be accepted as well).
Is there anyway I can make a dictionary with pointers in C#? Or some kind of assignment that changes the value of variable's target instead of changing the target to another value?
Thanks for the help.
You can do this using reflection. Get a list of the properties on the object of interest with typeof(Foo).GetProperties(). Your PropertyCorrectValues property can have type IDictionary<PropertyInfo, object>. Then use the GetValue and SetValue methods on PropertyInfo to perform the desired operations:
public bool CheckProperty(object myObjectToBeChecked, PropertyInfo p)
{
return p.GetValue(myObjectToBeChecked, null).Equals(PropertyCorrectValues[p]);
}
public void DoCorrection(object myObjectToBeCorrected, PropertyInfo p)
{
p.SetValue(myObjectToBeCorrected, PropertyCorrectValues[p]);
}
In addition to Ben's code I'd like to contribute the following code fragment:
Dictionary<string,object> PropertyCorrectValues = new Dictionary<string,object>();
PropertyCorrectValues["UserName"] = "Pete"; // propertyName
PropertyCorrectValues["SomeClass.AccountData"] = "XYZ"; // className.propertyName
public void CheckAndCorrectProperties(object obj) {
if (obj == null) { return; }
// find all properties for given object that need to be checked
var checkableProps = from props
in obj.GetType().GetProperties()
from corr in PropertyCorrectValues
where (corr.Key.Contains(".") == false && props.Name == corr.Key) // propertyName
|| (corr.Key.Contains(".") == true && corr.Key.StartsWith(props.DeclaringType.Name + ".") && corr.Key.EndsWith("." + props.Name)) // className.propertyName
select new { Property = props, Key = corr.Key };
foreach (var pInfo in checkableProps) {
object propValue = pInfo.Property.GetValue(obj, null);
object expectedValue = PropertyCorrectValues[pInfo.Key];
// checking for equal value
if (((propValue == null) && (expectedValue != null)) || (propValue.Equals(expectedValue) == false)) {
// setting value
pInfo.Property.SetValue(obj, expectedValue, null);
}
}
}
When using this "automatic" value correction you might also consider:
You cannot create a PropertyInfo object just by knowing the property name and independently of the declaring class; that's why I chose string for the key.
When using the same property name in different classes then you might need to change the code that is doing the actual assignment because the type between the correct value and the property type might differ.
Using the same property name in different classes will always perform the same check (see point above), so you might need a syntax for property names to restrict it to a specific class (simple dot notation, doesn't work for namespaces or inner classes, but might be extended to do so)
If needed you can replace the "check" and "assign" part with separate method calls, but it might be done inside the code block as stated in my example code.

TargetParameterCountException when enumerating through properties of string

I'm using the following code to output values of properties:
string output = String.Empty;
string stringy = "stringy";
int inty = 4;
Foo spong = new Foo() {Name = "spong", NumberOfHams = 8};
foreach (PropertyInfo propertyInfo in stringy.GetType().GetProperties())
{
if (propertyInfo.CanRead) output += propertyInfo.GetValue(stringy, null);
}
If I run this code for the int, or for the Foo complex type, it works fine. But if I run it for the string (as shown), I get the following error on the line inside the foreach loop:
System.Reflection.TargetParameterCountException: Parameter count mismatch.
Does anyone know what this means and how to avoid it?
In case anyone asks 'why are you enumerating through the properties of a string', eventually I hope to create a generic class which will output the properties of any type passed to it (which might be a string...).
In this case, one of the string's properties is the indexer for returning the character at the specified location. Thus, when you try to GetValue, the method expects an index but doesn't receive one, causing the exception.
To check which properties require index you can call GetIndexParameters on the PropertyInfo object. It returns an array of ParameterInfo, but you can just check the length of that array (if there are no parameters, it will be zero)
System.String has an indexed property that returns the char in the specified location. An indexed property needs an argument (index in this case) therefore the exception.
Just as reference... if you want to avoid the TargetParameterCountException when reading properties values:
// Ask each childs to notify also, if any could (if possible)
foreach (PropertyInfo prop in options.GetType().GetProperties())
{
if (prop.CanRead) // Does the property has a "Get" accessor
{
if (prop.GetIndexParameters().Length == 0) // Ensure that the property does not requires any parameter
{
var notify = prop.GetValue(options) as INotifyPropertyChanged;
if (notify != null)
{
notify.PropertyChanged += options.OptionsBasePropertyChanged;
}
}
else
{
// Will get TargetParameterCountException if query:
// var notify = prop.GetValue(options) as INotifyPropertyChanged;
}
}
}
Hope it helps ;-)

Categories

Resources