'T'不包含定义

14

可以执行以下操作吗(如果可以的话,我好像无法使其工作...暂时放弃约束)...

如果类型是推断出来的,问题在哪里?

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

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

使用上述代码后,我得到了以下错误:
“T”不包含“SpreadsheetLineNumbers”的定义,也没有接受类型为“T”的第一个参数的扩展方法“SpreadsheetLineNumbers”(您是否缺少使用指令或程序集引用?)

你需要在你的泛型方法上添加约束,因为当前 T 的类型是 object,而 object“不包含'SpreadsheetLineNumbers'的定义”。如果DuplicateSpreadsheetRowModel类或SpreadsheetRowModel类从一个共同的基类继承或实现相同的接口,请使用该基类作为约束。 - 3615
C# 泛型不是隐式类型。你的方法签名应该写成 GetGenericTableContent(someStringBuilder, 42),其中 T = int,这样才能被调用。显然,这会失败,因为 int 没有成员 SpreadsheetLineNumbers,所以 C# 正确地拒绝了你的代码。这就是约束的全部意义所在。 - Konrad Rudolph
啊,看来我误以为省略类型会在编译时推断出来,这正是微软所说的“编译器可以根据您传递的方法参数推断类型参数;它无法仅从约束或返回值推断类型参数。”和“编译器仅包括那些类型推断成功的泛型方法。”...所以我猜它成功的类型是哪些...嗯...如果它起作用了,我就会添加约束。 - Paul Zahra
1
编译器确实会在每个调用点推断泛型参数的类型,问题在于你的GetGenericTableContent方法声称适用于任何类型T,但它实际上只能应用于具有适当的SpreadsheetLineNumbers属性的类型。描述这种类型的方法是在T上添加一个基类或接口约束,以指定所需的成员。 - Lee
4个回答

32

不行。泛型类型必须在编译时确定。 请思考一下,编译器怎么可能知道保证类型 T 具有特定属性,例如 SpreadsheetLineNumbers?如果 T 是像 int 或者 object 这样的基本类型呢?

我们能否像这样调用方法:GetGenericTableContent(ref _, 999) 并将 T 设为 int 呢?

要解决这个问题,你可以先添加一个包含该属性的接口:

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

请让你的类继承自该接口:

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

然后我们使用通用类型约束来让编译器知道类型 T 继承自该接口,因此它必须包含并实现所有成员:

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>");
}

6

如果您无法为您的类型创建接口(或多个类型之间的公共接口):

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

使用方法:

GetGenericTableContent(ref outputTableContent, item, x => x.SpreadsheetLineNumbers);
< p >< em >(如果您不需要在方法中使用项引用,也可以仅传递SpreadSheetLineNumbers属性: void GetGenericTableContant<T>(ref StringBuilder outputTableContent, string lineNumbers))


3
实际上,如果您确信泛型T具有精确属性,则可以使用where(泛型类型约束)用于具有where T : MyClass的指定类。
例如,如果你有两个实体FooBoo
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;}
}

通过一些重构,我们可以创建一个BaseClass来保存公共属性:

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

并将FooBoo修改为:

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

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

如果您拥有一个泛型服务,并且其约束类型为where T : BaseModel,编译器将允许您获取或设置BaseModel的属性。
假设您想要为每个添加到数据库中的实体(FooBoo)设置CreateDateId属性,而不是使用服务器默认值:
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);           
    } 
}

0

为了使其适用于通用类型,您必须有一个通用类型约束,作为公共基类或接口声明 SpreadsheetLineNumbers 属性。

然而,我没有看到将此方法泛型化的优势。您也可以将参数键入为这个公共基类或接口。

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

如果 DuplicateSpreadsheetRowModel 是从 SpreadsheetRowModel 派生的,那么你可以直接将参数类型写成 SpreadsheetRowModel
此外,StringBuilder 是一个类,即引用类型。因此,不需要使用 ref 关键字,因为引用已经被传递了。(使用 ref 关键字将传递对引用的引用,只有在您想要在方法中替换现有的 StringBuilder 为新的并希望在调用方反映此更改时才有意义。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接