Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'T' does not contain a definition

Tags:

c#

generics

Is it possible to do the following (If so I can't seem to get it working.. forgoing constraints for the moment)...

If the type (because it's ommitted) is inferred, what's the problem?

private void GetGenericTableContent<T>(ref StringBuilder outputTableContent, T item)
{
    outputTableContent.Append("<td>" + item.SpreadsheetLineNumbers + "</td>");
}

// 'item' is either DuplicateSpreadsheetRowModel class or SpreadsheetRowModel class

With the above code I get the following error:

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

like image 422
Paul Zahra Avatar asked Sep 23 '16 11:09

Paul Zahra


3 Answers

No. Generic types must be known at compile time. Think about it for a minute, how could compiler know that it is guaranteed that type T has specific property, namely SpreadsheetLineNumbers? What if T is of primitive type such as int or object ?

What prevents us from calling the method like this: GetGenericTableContent(ref _, 999) with T as int here ?

To fix it you could first add an interface that contains the property :

public interface MyInterface 
{
    string SpreadsheetLineNumbers { get; set; }
}

And let your class inherit from this interface:

public class MyClass : MyInterface
{
    public string SpreadsheetLineNumbers { get; set; }
}

Then we use generic type constraints to let compiler know that the type T derives from this interface and therefore it has to contain and implement all its members:

private void GetGenericTableContent<T>(ref StringBuilder outputTableContent, T item) 
    where T : IMyInterface // now compiler knows that type T has implemented SpreadsheetLineNumbers
{
    outputTableContent.Append("<td>" + item.SpreadsheetLineNumbers + "</td>");
}
like image 90
Fabjan Avatar answered Nov 20 '22 12:11

Fabjan


Actually it is possible if you know for sure that the generic T has the exact property, using the where (generic type constraint) for a specified class with where T : MyClass.


For instance, if you have two entities Foo and Boo:

class Foo 
{
    public Guid Id {get; set;}
    public DateTime CreateDate {get; set;}
    public int FooProp {get; set;}
}

class Boo 
{
    public Guid Id {get; set;}
    public DateTime CreateDate {get; set;}
    public int BooProp {get; set;}
}

With a little refactor we can create a BaseClass that will hold the common properties:

class BaseModel
{
    public Guid Id {get; set;}
    public DateTime CreateDate {get; set;}        
}

And modify Foo and Boo to be:

class Boo : BaseModel
{
    public int BooProp {get; set;}
}

class Foo : BaseModel
{
    public int FooProp {get; set;}
}

And If you have a generic service with the constraint type of where T : BaseModel the compiler will allow you to get or set the BaseModel's properties.

Lets say that you want that for every entity (Foo or Boo) added to DB you will want to set the CreateDate and Id properties from code (and not from server default value):

public interface IGenericService<T>
{
    void Insert(T obj);
}

public class GenericService<T> : IGenericService<T> where T : BaseModel
{
    public void Insert(T obj)
    {
        obj.Id = Guid.NewGuid();
        obj.CreateDate = DateTime.UtcNow;
        this._repository.Insert(obj);           
    } 
}
like image 21
Shahar Shokrani Avatar answered Nov 20 '22 12:11

Shahar Shokrani


If you can't get make an interface for your type (or a common one between several types):

private void GetGenericTableContant<T>(ref StringBuilder outputTableContent, T item, Func<T, string> lineNumberAccessor)
{
     outputTableContent.Append("<td>" + lineNumberAccessor(item) + "</td>");
}

Usage:

GetGenericTableContent(ref outputTableContent, item, x => x.SpreadsheetLineNumbers);

(Or you could just pass the SpreadSheetLineNumbers property if you don't really need the item reference in your method: void GetGenericTableContant<T>(ref StringBuilder outputTableContent, string lineNumbers))

like image 6
romain-aga Avatar answered Nov 20 '22 10:11

romain-aga