0

I'm trying to write a generic method, and the code below gives errors.

Cannot convert type 'T2' to 'T1'

'T1' does not contain a definition for 'Action' and no extension method 'Action' accepting a first argument of type 'T1' could be found (are you missing a using directive or an assembly reference?)

private List<T2> FillChildControlOnSave<T1, T2>(
        ref T1 objEntity, ref List<T1> _entityParent, ref List<T2> _entityDetail)
{
    foreach (T2 c in _entityDetail)
    {
        if (c.Action == XERP.Entity.ActionMode.Add)            
            objEntity.PlnBOMDetails.Add(c);

        var tmp = objEntity.PlnBOMDetails
                     .Where(p => p.BOMDetailRecordID == c.BOMDetailRecordID && 
                                 p.BOMID == c.BOMID && 
                                 p.IsDeleted == false)
                     .FirstOrDefault();

        if (tmp != null)
           if (c.Action == Entity.ActionMode.Delete)            
               objController.Context.PlnBOMDetails.DeleteObject(tmp);            
    }

    return _entityDetail;

}

If I replace T1 and T2 with PlnBOMMaster,PlnBOMDetail then above syntax works fine. How to solve this generic method problem?

Grant Thomas
  • 44,454
  • 10
  • 85
  • 129
shamim
  • 6,640
  • 20
  • 85
  • 151
  • 1
    Notice: using `ref` is not very good idea. Underscore in parameter names is not very good idea. You are not using `_entityParent` parameter in your code. You don't need to pass `List` if you are only enumerating it. You don't need to use generics if you are working with concrete object properties. Just program to some abstraction instead (interface or base class). – Sergey Berezovskiy Apr 18 '13 at 07:49

3 Answers3

2

If you want to restrict T1 and T2 to certain classes or interfaces, you need to use generic constraints, like this:

private List<T2> FillChildControlOnSave<T1, T2>(ref T1 objEntity, 
                                                ref List<T1> _entityParent, 
                                                ref List<T2> _entityDetail)
    where T1 : PinBOMMaster
    where T2 : PinBOMDetail
{
    ...
}

Of course PinBOMMaster and PinBOMDetail can be replaced with an appropriate interface, such as this:

public interface IMaster<TDetail>
    where TDetail : IDetail
{
    List<TDetail> Details { get; }
}

public interface IDetail
{
    int RecordID { get; }
    int BOMID { get; }
    bool isDeleted { get; }
    Entity.ActionMode Action { get; set; }
}

public class PinBOMMaster : IMaster<PinBOMDetail>
{
    ...
}

public class PinBOMDetail : IDetail
{
    ...
}

private List<T2> FillChildControlOnSave<T1, T2>(ref T1 objEntity, 
                                                ref List<T1> _entityParent, 
                                                ref List<T2> _entityDetail)
    where T1 : IMaster<T2>
    where T2 : IDetail
{
    ...
}

Note: if your entities are created by a code generation tool, you'll have to use partial classes to apply the interface implementations.

Of course, you probably can't use objController.Context.PlnBOMDetails.Add(c). You'll have to replace it with generic code, like this:

// for DbContext
objController.Context.Set<T2>().Add(c);

// for ObjectContext
objController.Context.CreateObjectSet<T2>().AddObject(c);

Of course, you can also write your own method to do this. For example, the IDetail / IMaster interfaces could have an AddToContext(...) method which takes the context and inserts itself into the appropriate collection. Then in FillChildControlOnSave you just call c.AddToContext(objConctroller.Context);.

p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • But in this case we don't need generics, don't we? :) – Sergey Berezovskiy Apr 18 '13 at 07:38
  • There could still be a use for generics even in that case -- e.g. if you want the return type to exactly match the input type. – p.s.w.g Apr 18 '13 at 07:55
  • p.s.w.g thanks for reply,I can not use PlnBOMMaster, PlnBOMDetail in where statement because,it’s a common method ,I need to use this method in several time with my several entities – shamim Apr 18 '13 at 07:57
  • @shamim the same principle applies, although you'll have to create interfaces for your entities. See my updated answer. – p.s.w.g Apr 18 '13 at 08:04
  • p.s.w.g will you please fix this code syntax objController.Context.Set() .I don’t get any Set Method in context – shamim Apr 18 '13 at 10:03
  • @shamim I'm not sure what ORM you're using. [`Set()`](http://msdn.microsoft.com/en-us/library/gg696521(v=vs.103).aspx) is a method from Entity Framework's `DbContext` class. – p.s.w.g Apr 18 '13 at 13:11
  • p.s.w.g thanks for reply.I use EF 4.0 uses the ObjectContext.my question code syntax objEntity.PlnBOMDetails.Add(c); also need to corporate in generic method,help me to replace this syntax with generic one.thanks again – shamim Apr 19 '13 at 18:20
  • @shamim I've included more details in my answer for completeness. – p.s.w.g Apr 19 '13 at 18:32
  • p.s.w.g thanks for reply also thask for your new syntax ,my problem remains i apply your new context code in my delete condition but in add and update hope you see my problem description code there i need to bind child object with parent object not add or update occur directly with the context ,because after execute this method i need to work on parent object ,there i also need to get the child object too.so i bind child object with parent by using the sytax objEntity.PlnBOMDetails.Add(c); for this generic method i need to replace this syntax with generic one.thanks in advanced. – shamim Apr 20 '13 at 02:24
2

If you want to invoke members on instances of T1 and T2 then you have to tell the compiler something about those types:

private T2 Bar<T1,T2>(T1 actionable) where T1 : IActionable, T2 : IActionResult
{
    T2 result = actionable.Action();
    return result;
}

You can specify constraints on T1 and T2 using the where keyword after the method's arguments.

You can also specify things such as:

where T : new()  // has a default constructor
where T : struct // is a value type
where T : class  // is a reference type
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
1

You are looping throught a list of T2 with this line :

foreach (T2 c in _entityDetail)

Then you are accessing the property Detail of c in this line :

if (c.Action == XERP.Entity.ActionMode.Add)

How the compiler could know that the type T2 contain such a property?

You need to constraint the generic to be one kind of interface like this:

interface IPlnBOMDetail { XERP.Entity.ActionMode Action {get;}}
class PlnBOMDetail : IPlnBOMDetail {}

private List<T2> FillChildControlOnSave<T1, T2>(ref T1 objEntity, ref List<T1> _entityParent, ref List<T2> _entityDetail)
    where T2 : IPlnBOMDetail 

The same for all the rest of your code.

A side note: using ref parameter is a code smell.
I advise you to read this topic : When to use ref and when it is not necessary in C#.
TL;DR : Jon Skeet said

you almost never need to use ref/out

Community
  • 1
  • 1
Cyril Gandon
  • 16,830
  • 14
  • 78
  • 122
  • Scorpi0 thanks for reply,I can not use PlnBOMMaster, PlnBOMDetail in where statement because,it’s a common method ,I need to use this method in several time with my several entities – shamim Apr 18 '13 at 07:59
  • @shamim in that case you need to get those types to implement interfaces which provide the required operations, then constrain your type parameters to those interfaces. It's either that or do some truly horrible things with reflection or the DLR to go hunting for a method which may or may not exist (and sacrifice type safety along the way). – Matthew Walton Apr 18 '13 at 08:26