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:
- 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.
- 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
.
Get the
PropertyInfo
from the given typeT
with the name of the propertywith that
PropertyInfo
you can useGetValue
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
Why Does C# Limit the Set of Types That Can Be Declared as Const
When Should I Use Gc.Suppressfinalize()
Converting File into Base64String and Back Again
Convert File Path to a File Uri
.Contains() on a List of Custom Class Objects
Enable Options Header for Cors on .Net Core Web API
How to Capture the Mouse Move Event
Can You Specify Format for Xmlserialization of a Datetime
How to Make a Property Protected and Internal in C#
Practical Uses for the "Internal" Keyword in C#
How to Keep a .Net Console App Running
Displayname Attribute VS Display Attribute
Run Code Once Before and After All Tests in Xunit.Net
How to Prevent Windows from Entering Idle State