Reputation: 103
I have two different classes. And, those two different classes have multiple properties as well. Consider the following two example classes,
public Class1{
public string Key;
public string value;
}
public Class2{
public string Key;
public string value;
}
Note: For example, I added the class like above. But, in reality, the two classes should have different values with the same name.
These classes should be a member of the list like below,
List<Class1> list1 = new List<Class1>();
List<Class2> list2 = new List<Class2>();
So, to process these list I need a two different functions something like below,
private string GetStrValue(List<Class1> values)
{
string toRet = string.Empty;
if (values == null)
return toRet;
foreach (Class1 val in values)
{
if (!string.IsNullOrWhiteSpace(val.Key)) {
toRet = val.value;
break;
}
}
return toRet;
}
And, the similar function to process the Int class as well. So, I planned to use the generic. I have written the code like below,
private string GetValue<T>(List<T> listValue)
{
string toRet = string.Empty;
if (listValue == null)
return toRet;
foreach (T iter in listValue)
{
if (!string.IsNullOrWhiteSpace(iter.Key)) {
toRet = val.Name;
break;
}
}
return toRet;
}
But, the code does not compile. I'm facing the below error.
Severity Code Description Project File Line Suppression State
Error CS1061 'T' does not contain a definition for 'Name' and no accessible extension method 'Name' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?)
It would be much-appreciated anyone helping on this.
Thank you,
Upvotes: 1
Views: 223
Reputation: 1388
As nvoigt mentioned, in this situation we have to use 'Interface' concept.
Define your classes and interface as below:
public interface IKeyValue
{
public string Key { get; set; }
public string Value { get; set; }
}
public class A : IKeyValue
{
public string Key { get; set; }
public string Value { get; set; }
//other properties...
}
public class B : IKeyValue
{
public string Key { get; set; }
public string Value { get; set; }
//other properties...
}
And your method which is going to use 'Key' and 'Value' should be like this:
private string GetValue<T>(List<T> listValue) where T: IKeyValue
{
string toRet = string.Empty;
if (listValue == null)
return toRet;
foreach (T iter in listValue)
{
if (!string.IsNullOrWhiteSpace(iter.Key))
{
toRet = iter.Value;
break;
}
}
return toRet;
}
'where T: IKeyValue' in method definition means type T is 'IKeyValue' that cause I can access the 'Key' and 'Value' in context (just those that are in IKeyValue interface)
This is how you can use it:
List<IKeyValue> keyValues = new List<IKeyValue>
{new A{Key="a",Value="b"}, new B{Key="x",Value="y"}};
List<A> listA = new List<A>
{ new A { Key = "h", Value = "b" }, new A { Key = "u", Value = "m" } };
List<B> listB = new List<B>
{ new B { Key = "h", Value = "b" }, new B { Key = "u", Value = "m" } };
string resultListInterface = GetValue(keyValues); //valid
string resultListA = GetValue(listA); //valid
string resultListB = GetValue(listB); //valid
For naming convention I change property name from 'value' to 'Value'
Upvotes: 2
Reputation: 197
You can make it completely generic using Reflection (refer Reflection | PropertyInfo). This way you would be able to handle any classes coming to you. Please refer to the sample code below:
private string GetValue<T>(List<T> listValue)
{
string toRet = string.Empty;
if (listValue == null)
return toRet;
PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
//Since you have mentioned all classes will have "Key" and "Value" and you need to use that only
//To make it completely generic you can maybe get this as input to this function
PropertyInfo keyProperty = properties.FirstOrDefault(x => x.Name.Equals("Key", StringComparison.OrdinalIgnoreCase));
PropertyInfo valueProperty = properties.FirstOrDefault(x => x.Name.Equals("Value", StringComparison.OrdinalIgnoreCase));
if (keyProperty == null || valueProperty == null)
return toRet;
foreach (T iter in listValue)
{
var keyData = keyProperty.GetValue(iter, null);
if (keyData != null && !string.IsNullOrWhiteSpace(keyData.ToString()))
{
toRet = valueProperty.GetValue(iter, null).ToString();
break;
}
}
return toRet;
}
Upvotes: 2
Reputation: 1983
You have 2 options interface
or father class
. Bot 2 ways require where T: interfaceName
or where T: fatherClassName
syntax
For example with interface:
public interface IClass
{
string Key { get; set; }
string Name { get; set; }
string value { get; set; }
}
public class Class1 : IClass
{
public string Key { get; set; }
public string value { get; set; }
public string Name { get; set; }
}
public class Class2 : IClass
{
public string Key { get; set; }
public string value { get; set; }
public string Name { get; set; }
}
Then your generic class would be
private string GetValue<T>(List<T> listValue) where T : IClass
{
string toRet = string.Empty;
if (listValue == null)
return toRet;
foreach (T val in listValue)
{
if (!string.IsNullOrWhiteSpace(val.Key))
{
toRet = val.Name;
break;
}
}
return toRet;
}
Upvotes: 3
Reputation: 77285
You have said "This code works for every type T" and yet you expect your Type T to have a property called Name, which many many types do not have. Generics do not work that way.
If you want to do something with your instance of type T that requires it to have certain properties, you need to tell the compiler that T is constrained to types that have those properties.
In your case you will need to write an interface common to both of your classes and the add that interface to your generic T definition.
This is well explained (including a good example with code) in the Microsoft documentation here
Upvotes: 4