Accessing Properties Through Generic Type Parameter

Accessing properties through Generic type parameter

Interface solution

If you can add an interface to your object you can use that. For example you could define:

 public interface IName
{
string Name { get; }
}

Then your repository could be declared as:

class Repository<T> where T:class, IName
{
public IQueryable<T> SearchExact(string keyword)
{
return db.GetTable<T>().Where(i => i.Name == keyword);
}
}

Alternate interface solution

Alternatively you could put the "where" on your SearchExact method by using a second generic parameter:

class Repository<T> where T:class
{
public IQueryable<T> SearchExact<U>(string keyword) where U: T,IName
{
return db.GetTable<U>().Where(i => i.Name == keyword);
}
}

This allows the Repository class to be used with objects that don't implement IName, whereas the SearchExact method can only be used with objects that implement IName.

Reflection solution

If you can't add an IName-like interface to your objects, you can use reflection instead:

class Repository<T> where T:class
{
static PropertyInfo _nameProperty = typeof(T).GetProperty("Name");

public IQueryable<T> SearchExact(string keyword)
{
return db.GetTable<T>().Where(i => (string)_nameProperty.GetValue(i) == keyword);
}
}

This is slower than using an interface, but sometimes it is the only way.

More notes on interface solution and why you might use it

In your comment you mention that you can't use an interface but don't explain why. You say "Nothing in common is present in the three models. So i think making an interface out of them is not possible." From your question I understood that all three models have a "Name" property. In that case, it is possible to implement an interface on all three. Just implement the interface as shown and ", IName" to each of your three class definitions. This will give you the best performance for both local queries and SQL generation.

Even if the properties in question are not all called "Name", you can still use the nterface solution by adding a "Name" property to each and having its getter and setter access the other property.

Expression solution

If the IName solution won't work and you need the SQL conversion to work, you can do this by building your LINQ query using Expressions. This more work and is significantly less efficient for local use but will convert to SQL well. The code would be something like this:

class Repository<T> where T:Class
{
public IQueryable<T> SearchExact(string keyword,
Expression<Func<T,string>> getNameExpression)
{
var param = Expression.Parameter(typeof(T), "i");
return db.GetTable<T>().Where(
Expression.Lambda<Func<T,bool>>(
Expression.Equal(
Expression.Invoke(
Expression.Constant(getNameExpression),
param),
Expression.Constant(keyword),
param));
}
}

and it would be called thusly:

repository.SearchExact("Text To Find", i => i.Name)

How to access Property of generic member in generic class

The Other Answers are right, but...

I just want to point out: while those other answers promote valid C# code, they make the generic aspect of you implementation superflous. You don't need generics anymore:

Given a base class or interface like

public interface IHasAddress
{
string Address { get; }
}

you don't need a Generic class anymore for what you are trying to achive (from what i can tell by the code you provided):

public class NotSoGenericClass
{
public GenericClass(IHasAddress obj)
{
DynamicObject = obj;
}

public IHasAddress DynamicObject { get; set; }
}

So as you can see, you can easily implement the desired behaviour w/o generics.

For you as a Beginner, i'd recommend the following basic rules when it comes to generics:

  1. When you think you have to use generics, force yourself to consider abstraction via interfaces, abstract classes or base classes first. This often leads to simpler and cleaner solutions.
  2. Same goes with Reflection. When you think you need Reflection, consider generics (Rule 1 is valid at that point to)

But Nothings wrong with Generics, its just more complex and often not needed. Compare the class above with the generic solution:

public class GenericClass<T> where T : IHasAddress  // just for the sake of generics
{
public GenericClass(T obj)
{
DynamicObject = obj;
}

public T DynamicObject { get; set; }
}

Looks more complex and doesn't add any benefit, does it? Also note that you need a Interface/baseclass no matter what. Otherwise, you could also use Reflection (not recommended).

To actually answer your question

The precise answer to your question is:

You have to define that you generic parameter has to be assignable to IHasAddress using

public class GenericClass<T> where T : IHasAddress
^^^^^^^^^^^^^^^^^^^^^
This Part

This way, the compiler knows that T inherits or is of type IHasAddress or what ever you define. You can also pass multiple types at this place which adds mor flexibility when it comes to designing your interfaces.

Maybe, there are points to consider in your usecase which are not obvious from the information you provided in the question. In that case, feel free to add some details and i'll be happy to deep dive into those as well.

How to get all properties of a class with generic type parameter

You can use the GetGenericTypeDefinition method to get the open form of the generic type and then compare that to VehicleCollection<> (the open form) like this:

var vehicleProperties = typeof(Program).GetProperties()
.Where(p =>
p.PropertyType.IsGenericType &&
p.PropertyType.GetGenericTypeDefinition() == typeof(VehicleCollection<>))
.ToList();

IsGenericType is used to make sure that the property type is generic.

Accessing properties of generic types in typescript?

If you know the type constraint has a property named property you can define an interface which defines the property and then use a constraint that tell E extends it. Then you will have access to that property without casting it.

interface WithProperty{
property:string;
}

function test<E extends WithProperty>(o:E):string {
return o.property; // or o["property"] is valid access.
}

Playground

Edit :
Since you updated your example. There is another way of doing that which is to use keyword keyof. Also using this one doesn't require knowledge of the properties. I have modified your example like below :

export function search<E>(query:string='', entities:E[], exclude:string[]=[]) {
const { isArray } = Array
type EKey = keyof E;
query = query.toLowerCase();

let keys : EKey[] = []
if (entities.length > 0) {
keys = excludeKeys<E>(entities[0], exclude)
}

return entities.filter(function (e:E) {
return keys.some((key =>{
const value = e[key];
if (isArray(value)) {
return value.some(v => {
return v.toLowerCase().includes(search);
});
}
else if (!isArray(value)) {
return new String(value).toLowerCase().includes(query);
}
})
});
}

For the excludeKeys part of the code casting becomes inevitable because of this looong going discussion.

export function excludeKeys<E>(entity: E, exclude: string[]) {
const keys: string[] = Object.keys(entity);
return <(keyof E)[]>keys.filter((key) => {
return exclude.indexOf(key) < 0;
});
}

Playground

How to get property of generic type?

Your code assumes that every type T used with your class may have an id property.

As pointed out by Jon Sharpe in his comment on your question, the correct approach, both in terms of type safety and expressiveness, is to declare this assumption as a type constraint, making it explicit and known to the type checker.

The way to do this is to use the type constraint syntax on your generic type parameter.

For example, you could write

export interface MayHaveId {
id?: number; // you can use any type, number is just an example
}

export abstract class BaseService<T extends MayHaveId> {
saveItem(item: T) {
if (item.id !== undefined) {
console.log(item.id);
}
}
}

Here we've defined an interface to make the code more readable, but a type literal would work as well.

Note that the id property is declared optional since your logic does not require it to have a value.

If you only intend for the base class to be used with type arguments which do have an id then you should make the property required. This will make the code easier to understand and will catch more errors at compile time by ensuring that it is only used with such types in the first place.

Shahbaaz asked about dynamic properties in a comment.

We can define a type with a dynamic property by using a parametric generic type

type WithProperty<K extends string, V = {}> = {
[P in K]: V
}

We can consume this type

function withProperty<T, K extends string, V> (x: T, properties: WithProperty<K, V>) {
return Object.assign(x, properties);
}

How to access Properties of a class from a Generic Method - C#

Another possibility would be to use System.Reflection.

  1. Get the PropertyInfo from the given type T with the name of the property

  2. with that PropertyInfo you can use GetValue to get the corresponding value of that property.

Here is a small test programm to exemplify this:

public class ClassA
{
public int CustID { get; set; }
public string Name { get; set; }
}

public class ClassB
{
public int CustID { get; set; }
public string Age { get; set; }
}
public static void ProceesData<T>(IList<T> param1, string date1)
{
Parallel.ForEach(param1, (currentItem) =>
{
// I want to aceess CustID property of param1 and pass that value to another function
var value = typeof(T).GetProperty("CustID").GetValue(currentItem);
Console.WriteLine("Value: " + value);
});
}
public static void Main(string[] args)
{
List<ClassA> test = new List<ClassA>();

test.Add(new ClassA { CustID = 123 });
test.Add(new ClassA { CustID = 223 });
test.Add(new ClassA { CustID = 323 });

ProceesData<ClassA>(test, "test");
}

EDIT

To make it a little more universal you could just pass the parameter name into the method:

public static void ProceesData<T>(IList<T> param1, string date1, string parameter)
{
Parallel.ForEach(param1, (currentItem) =>
{
// I want to aceess CustID property of param1 and pass that value to another function
var value = typeof(T).GetProperty(parameter).GetValue(currentItem);
Console.WriteLine("Value: " + value);
});
}

Now you can decide what parameter you want to use:

 ProceesData<ClassA>(test, "test", "Name");

or

 ProceesData<ClassB>(test, "test", "Age");

As suggested by Gusman you could speed up a little by getting the PropertyInfo just once before the loop:

PropertyInfo pi = typeof(T).GetProperty(parameter);
Parallel.ForEach(param1, (currentItem) =>
{
// I want to aceess CustID property of param1 and pass that value to another function
var value = pi.GetValue(currentItem);
Console.WriteLine("Value: " + value);
});

EDIT

Apparently performance seems to be an issue for you. So here is a comparison. You can try it on your own if you have a minute to wait. If we measure on the access time of the property:

public static void ProceesDataD<T>(IList<T> param1, string date1)
{
Parallel.ForEach(param1, (currentItem) =>
{
dynamic obj = currentItem;
int custId = obj.CustID;
});
}
public static void ProceesData<T>(IList<T> param1, string date1) where T : ICust
{
Parallel.ForEach(param1, (currentItem) =>
{
var value = currentItem.CustID;
});
}
public static void ProceesData<T>(IList<T> param1, string date1, string parameter)
{

PropertyInfo pi = typeof(T).GetProperty(parameter);
Parallel.ForEach(param1, (currentItem) =>
{
var value = pi.GetValue(currentItem);
});
}
public static void Main(string[] args)
{
List<ClassA> test = new List<ClassA>();
List<A> testA = new List<A>();

Stopwatch st = new Stopwatch();

for (int i = 0; i < 10000; i++)
{
test.Add(new ClassA { CustID = 123, Name = "Me" });
testA.Add(new A { CustID = 123, Name = "Me" });
}

st.Start();
ProceesData<ClassA>(test, "test", "CustID");
st.Stop();
Console.WriteLine("Reflection: " + st.ElapsedMilliseconds);

st.Restart();
ProceesData<A>(testA, "test");
st.Stop();
Console.WriteLine("Interface: " + st.ElapsedMilliseconds);

st.Restart();
ProceesDataD<ClassA>(test, "test");
st.Stop();
Console.WriteLine("Dynamic: " + st.ElapsedMilliseconds);
}

Disclaimer: use the code passages to measure the time only one at the time. Do not run the program as it is but each single test on it's own.



Related Topics



Leave a reply



Submit