C#:传递一个通用对象

26
我希望有一个通用的打印函数...PrintGeneric(T)...在下面的情况中,我缺少了什么?
像往常一样,感谢您的帮助和见解...
public interface ITest
{}

public class MyClass1 : ITest
{
    public string myvar = "hello 1";
}

public class MyClass2 : ITest
{
    public string myvar = "hello 2";
}

class DoSomethingClass
{

    static void Main()
    {
        MyClass1 test1 = new MyClass1();
        MyClass2 test2 = new MyClass2();

        Console.WriteLine(test1.myvar);
        Console.WriteLine(test2.myvar);             
        Console.WriteLine(test1.GetType());

        PrintGeneric(test1);
        PrintGeneric<test2.GetType()>(test2);
    }

    // following doesn't compile
    public void PrintGeneric<T>(T test)
    {
        Console.WriteLine("Generic : " + test.myvar);
    }
}

1
"var"不能作为变量名,因为它是一个关键字。你能展示一下你的实际代码吗? - Ash Burlaczenko
3
@AshBurlaczenko 不正确。var 是 C# 3.0 中添加的上下文关键字,您可以将其用作标识符名称。同样,您可以在大多数情况下将 yieldselectfrom 等用作标识符名称。 - Mehrdad Afshari
1
实际上,“var”在不使用Console.WriteLine的情况下确实可以工作...但是我的错误在于将关键字用作标识符名称... - user1229895
2
在这种情况下,我们成功地猜出了您的问题所在以及错误原因。然而,您不应过分依赖我们的心灵感应能力;-) 一般来说,如果您明确指示代码行中发生错误的位置,并发布确切的错误消息,将会让每个人的生活更轻松。 - Péter Török
哈哈,抱歉,我以后会尽量这样做的。谢谢! - user1229895
7个回答

32

编译不通过是因为T可能是任何类型,而不是所有类型都有“myvar”字段。

你可以在ITest上创建一个名为myvar的属性:

public ITest
{
    string myvar{get;}
}

并将其作为属性实现在类中:

public class MyClass1 : ITest
{
    public string myvar{ get { return "hello 1"; } }
}

然后在您的方法上放置一个通用约束:

public void PrintGeneric<T>(T test) where T : ITest
{
    Console.WriteLine("Generic : " + test.myvar);
}

但是在这种情况下,说实话,你最好只传递一个ITest:

public void PrintGeneric(ITest test)
{
    Console.WriteLine("Generic : " + test.myvar);
}

我向ITest添加了一个属性,但它告诉我需要在MyClass1/MyClass2中实现它...这很有道理,但然后我收到一个错误,说它已经包含了myvar的定义。 - user1229895
哦,是的,你需要在实现类中将字段更改为属性。我更新了我的答案来向你展示。 - jonnystoten
我在Main函数中尝试使用PrintGeneric<test2.GetType()>(test2)来调用泛型,但是出现了“只有赋值、调用、递增、递减可以作为语句”的错误提示。我尝试使用PrintGeneric(test1)来调用PrintGeneric(ITest test),但是出现了“非静态字段需要一个对象引用”的错误提示。 - user1229895
好的,我明白了。我需要改成'public static void PrintGeneric(ITest test)'...但我仍然不确定为什么PrintGeneric<T>说只能将赋值、调用和增量用作语句。 - user1229895

8

您至少缺少了一些东西:

  • Unless you're using reflection, the type arguments need to be known at compile-time, so you can't use

    PrintGeneric<test2.GetType()>
    

    ... although in this case you don't need to anyway

  • PrintGeneric doesn't know anything about T at the moment, so the compiler can't find a member called T

选项:

  • Put a property in the ITest interface, and change PrintGeneric to constrain T:

    public void PrintGeneric<T>(T test) where T : ITest
    {
        Console.WriteLine("Generic : " + test.PropertyFromInterface);
    }
    
  • Put a property in the ITest interface and remove the generics entirely:

    public void PrintGeneric(ITest test)
    {
        Console.WriteLine("Property : " + test.PropertyFromInterface);
    }
    
  • Use dynamic typing instead of generics if you're using C# 4


1
Jon,在您第二个代码片段中出现了打字错误吗?您说要删除范型,但该方法上仍有<T>? - Steve
2
@SteveHaigh:唉,这就是我一边打字一边接电话的下场。已经修复好了,谢谢。 - Jon Skeet
尝试第一种选项...我向 ITest 中添加了 "string myvar {get;}",但是出现错误,称 MyClass1/MyClass2 未实现接口成员 'ITest.myvar'...将 "string myvar {get;}" 添加到 MyClass1/MyClass2 中会导致编译错误,说它已经定义了? - user1229895
@user1229895:你不能像那样声明一个具体的属性...但这与泛型无关。你可以使用自动实现属性在MyClass1中实现它,如public string myvar { get; private set; }。如果这还不够帮助,请提出一个单独的问题。 - Jon Skeet
据我所知,像这样的基本功能不建议使用动态类型,它应该是你的最后手段。 - Ozkan
1
@Ozkan:嗯,它在这里是列表中的最后一个...虽然我会说,使用动态类型而不是手动编写反射可以将反射代码有效地卸载到经过充分测试的代码库中,这可能是有用的。 - Jon Skeet

2
在你的通用方法中,T只是类型的占位符。然而,编译器本身并不知道运行时使用的具体类型,因此它不能假设它们将具有var成员。
通常绕过这个问题的方法是在方法声明中添加通用类型约束,以确保使用的类型实现了特定的接口(在你的情况下,可能是ITest):
public void PrintGeneric<T>(T test) where T : ITest

接下来,在该方法内部,接口的成员将直接可用。然而,ITest目前为空,您需要在其中声明共同的内容,以便在方法内使用。


2
您需要提供有关泛型类型 T 的更多信息。在您当前的 PrintGeneric 方法中,T 可以是一个 string,它没有一个 var 成员。
您可能希望将 var 更改为属性而不是字段。
public interface ITest
{
    string var { get; }
}

并且在PrintGeneric方法中添加一个约束条件where T: ITest


1

尝试

public void PrintGeneric<T>(T test) where T: ITest
{
    Console.WriteLine("Generic : " + test.@var);
}

如@Ash Burlaczenko所说,您不能以关键字命名变量,如果您确实想要这个前缀与@符号一起使用来避免关键字。

1
这行代码不会起作用,因为 ITest 没有名为 var 的成员。 - Mehrdad Afshari
只有当ITest指定了属性var时才能工作,当然,我不确定在示例中是否这样做。 - Alexander R
我收到了一些错误信息... CS0120:非静态字段/方法/属性“DoSomethingClass.PrintGeneric<T>(T)”需要对象引用 CS0201:只能使用赋值、调用、增量、减量、新对象作为语句 CS0019:运算符<不能应用于类型为Method Group的操作数 CS1061:T不包含定义myvar的定义 - user1229895

1
你需要在接口中定义一些内容,例如:
public interface ITest
{
    string Name { get; }
}

在你的类中实现ITest

public class MyClass1 : ITest
{
    public string Name { get { return "Test1"; } }
}

public class MyClass2 : ITest
{
    public string Name { get { return "Test2"; } }
}

然后将通用的Print函数限制为ITest
public void Print<T>(T test) where T : ITest
{
}

看一下我在Ash的评论中的回复。你可以使用var作为标识符名称,就像yield等一样,尽管这可能不是一个好主意。它是一个上下文关键字。尝试int var;验证一下自己。 - Mehrdad Afshari
@MehrdadAfshari,啊..我之前不知道这个。今天我学到了。但是,这不是我会真正去做的事情 :) - Moo-Juice

0

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