2

I am trying to create a Generic function that handles either of my 2 models. Note that both of these models have the same exact properties...

For example, in the below code the intellisense has no idea that there is a property called Price in T even though both 'NewProduct' and 'OldProduct' have this property. How can I specify to VS the two Types that I want to be able to pass in? IList<NewProduct>, IList<OldProduct>

public static IList<T> GenericFunction<T>(IList<T> objList)
{
    IList<T> filteredData = objList.Where(p => p.Price > 0));
}
Blake Rivell
  • 13,105
  • 31
  • 115
  • 231
  • I think http://stackoverflow.com/questions/965580/c-sharp-generics-syntax-for-multiple-type-parameter-constraints is wrong duplicate - http://stackoverflow.com/questions/27174104/c-sharp-generic-passing-different-objects-with-same-properties could be better, but it asks about queryable (also answer Interface/dynamic applies to this case...) – Alexei Levenkov Mar 08 '16 at 23:10

5 Answers5

5

Both types need the same interface or common base class, called here ProductBase. You can then use a generic constraint with the where keyword:

public static IList<T> GenericFunction<T>(IList<T> objList) 
  where T : ProductBase
{
    IList<T> filteredData = objList.Where(p => p.Price > 0));
}

This works, if ProductBase defines a property Price.

ventiseis
  • 3,029
  • 11
  • 32
  • 49
2

In order to make it work, you need either a common base class or a common interface implemented in both product types.

public interface IProduct
{
    string Name { get; set; }
    decimal Price { get; set; }
}

Then you can add a generic type constraint:

public static IList<P> GenericFunction<P>(IList<P> objList)
    where P : IProduct // Here you can specify either the base class or the interface.
{
    return objList
        .Where(p => p.Price > 0)
        .ToList();
}    

Now C# knows that the generic type P has Name and Price properties.


Note: Instead you could just type the parameter list and the return type as IList<IProduct>; however, IList<OldProduct> and IList<NewProduct> are not assignment compatible with it.


UPDATE: You can instantiate a generic type, if it has a default constructor (i.e. a constructor with an empty parameter list or no explicit constructor declaration at all). You then need to add new() to the generic type constraints:

where P : IProduct, new()

You can then simply create a new object with:

P newObject = new P();
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • You should remove `public` from your interface declaration. Interfaces do not declare their members' access level and any class that implements this interface will always need to declare the members as public. Interface == Contract. – Tom Mar 08 '16 at 22:56
  • @OlivierJacot-Descombes thank you for clearing this up for me. All of what you said makes sense. I ran across a scenario where I need to instantiate a new object of P inside of this function. Doesn't this restrict me to using a Generic function like you posted above? Or could I still make it work with a simple function that returns IList? I feel like since I need to instantiate a new object of P and then add it to the return value isn't a Generic function with InterfaceorBaseClass my only option here? – Blake Rivell Mar 09 '16 at 15:41
  • @OlivierJacot-Descombes To clarify my previous comment: I noticed that I have to do something like: P ret = (P)Activator.CreateInstance(typeof(P)); in order to instantiate a new object of P and add it to my IList

    retVal? Does this require me to use a Generic function? I don't believe there are any other workarounds are there?

    – Blake Rivell Mar 09 '16 at 15:47
  • I added an explanation on how to instantiate a generic type. – Olivier Jacot-Descombes Mar 09 '16 at 17:04
2

You can use the where generic constraint https://msdn.microsoft.com/en-us/library/bb384067.aspx

You need some common base class or interface that they both extend/implement. You can define multiple constraints but they must be related.

interface IProduct
{
    double Price { get; }
}

public static IList<T> GenericFunction<T>(IList<T> objList) where T : IProduct
{
    IList<T> filteredData = objList.Where(p => p.Price > 0));
}
Tom
  • 2,360
  • 21
  • 15
  • 1
    This is starting to make sense to me now, and I believe this is the correct answer. But if I use an interface there is no point in making a generic function correct? Just make a function that accepts my interface. Make sense? – Blake Rivell Mar 08 '16 at 22:43
  • 1
    Not necessarily true, what's good about this is that the return value is of the actual type (e.g. List). Using just an interface will always return List. – Tom Mar 08 '16 at 22:46
2

You should use an interface for this. Create an interface with the properties that you need:

public interface MyInterface
{
    string Name { get; set; }
    string Color { get; set; }
}

These properties should be the ones that your models share. Then in your model you must implement the interface:

public class MyModel : MyInterface

Then make your method:

public void MyFunction(List<MyInterface> myModel)
Dave Greilach
  • 885
  • 5
  • 9
1

You should look into abstract classes, extensions, and polymorphism. Make an abstract class with a price variable then extend your two classes from it. Then use the abstract class as a parameter.

Seth Kitchen
  • 1,526
  • 19
  • 53