shamim
shamim

Reputation: 6768

Generics and types values in my method show error

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?

Upvotes: 0

Views: 153

Answers (3)

p.s.w.g
p.s.w.g

Reputation: 149020

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);.

Upvotes: 2

Cyril Gandon
Cyril Gandon

Reputation: 17058

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

Upvotes: 1

Drew Noakes
Drew Noakes

Reputation: 310917

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

Upvotes: 2

Related Questions