Reputation: 6768
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
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
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
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