将当前对象类型传递到基类构造函数调用中

9
我该如何获取继承类的Type并将其传递到同样继承的基类构造函数中?请参见下面的代码示例:
// VeryBaseClass is in an external assembly
public abstract class VeryBaseClass
{
    public VeryBaseClass(string className, MyObject myObject)
    {

    }
}

// BaseClass and InheritedClass are in my assembly
public abstract class BaseClass : VeryBaseClass
{
    public BaseClass(MyObject myObject) :
        base(this.GetType().Name, myObject) // can't reference "this" (Type expected)
    {

    }
}

public class InheritedClass : BaseClass
{
    public InheritedClass(MyObject myObject)
    {

    }
}

由于对象尚未完成构造且不存在,因此无法引用this,所以该行base(typeof(this).Name, myObject)不起作用。

是否有可能获取当前正在构建对象的Type

编辑:

根据orsogufo的建议更正了示例,但仍然无法工作,因为this未定义。

编辑2:

只是为了澄清,我想最终将"InheritedClass"传递到VeryBaseClass(string className, MyObject myObject)构造函数中。


1
你为什么想要这样做?构造函数不应该关心它是被调用来实例化自己的类还是派生类。 - AakashM
1
我认为你没有理解重点。我使用的框架的基类定义在外部程序集中,需要将其自身的类的字符串表示形式传递给构造函数。到目前为止,我一直按照预期的方式(作为字符串)进行操作,但出于健全性考虑,我更希望它能在运行时计算出来。 - Codesleuth
1
哦,我明白了,这不是你的类。但我仍然认为它很糟糕,因为它关心它 :) - AakashM
6个回答

19

啊哈!我找到了一个解决方案。你可以使用泛型来实现:

public abstract class VeryBaseClass
{
    public VeryBaseClass(string className, MyObject myObject)
    {
        this.ClassName = className;
    }

    public string ClassName{ get; set; }
}
public abstract class BaseClass<T> : VeryBaseClass
{
    public BaseClass(MyObject myObject)
        : base(typeof(T).Name, myObject)
    {
    }
}

public class InheritedClass : BaseClass<InheritedClass>
{
    public InheritedClass(MyObject myObject) 
        : base(myObject)
    {

    }
}

哦,我的天啊,太聪明了!当我砍死我的TFS源代码控制布局后,我会测试它 :) - Codesleuth
太好了,它可用!虽然这意味着我需要做很多工作来改变我的现有类,但最终这将是值得的。谢谢 :) - Codesleuth
3
突然出现了CRTP(亦称为奇妙的递归模板模式)! :) (请参见https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) - Macke
通过上面的代码,您可以创建一个类似于以下内容的类,在其中将不同的内容传递给扩展类。 public class InheritedClass : BaseClass<int> { .. } 为了强制仅传递扩展类的类型,您可能希望添加一个where子句。public abstract class BaseClass<T> : VeryBaseClass where T:BaseClass<T> { ... } - timothy
这种技术的问题在于没有保证 typeof(T) 总是能正确地识别正在构建的对象的 Type。例如,对于进一步继承自 InheritedClass 的任何类,此答案都将失败。标记所有直接继承者为 sealed 或添加 Type 接受的 protected 构造函数可能有所帮助,但是无法强制执行。即使添加了 @timothy 建议的 where 约束条件仍然很脆弱,因为它取决于每个具体的 T 正好派生自 BaseClass<T>,出于同样的基本原因,这仍然不一定是 T - Glenn Slayden

6

我以前在Google Wave Robot .NET API中遇到过完全相同的问题,当时我想让构造函数根据属性传递一些值。您可以查看派生类型基本类型的代码中我的解决方案。基本上,我将一个委托传递给基础构造函数,然后调用该委托并将"this"传递给委托。因此,在您的情况下,您会有:

public VeryBaseClass(Func<VeryBaseClass, string> classNameProvider)
{
    this.name = classNameProvider(this);
}

并且

public BaseClass() : base(FindClassName)
{
}

private static string FindClassName(VeryBaseClass @this)
{
    return @this.GetType().Name;
}

我很丑,但我很实用。

编辑:仅当您可以按照所示更改基类构造函数时,此方法才有效;如果无法更改,则我不确定它是否可行 :(


我以前从未见过 @this,而且似乎也让谷歌感到困惑。它是否只允许调用一个没有参数的方法,并自动将当前对象放置其中?(这就是我对它的理解) - Codesleuth
1
不,它前面的@只是告诉编译器将“this”视为标识符(以区分它与关键字)。请参阅此链接:http://msdn.microsoft.com/en-us/library/aa664670%28VS.71%29.aspx。您可以将“@this”替换为任何参数名称。 - Paolo Tedesco
参数“1”:无法将“方法组”转换为“字符串”。 我想在这里提到,尽管我的示例仅显示基类使用一个参数,但我的实际实现使用了更多参数。我会更新问题以反映这一点。 - Codesleuth
@CodeSleuth:你按照所示更改了VeryBaseClass构造函数吗?如果是这样,它就不应该再期望一个字符串了... - Jon Skeet
啊,我刚刚看到你无法控制基类。在这种情况下,这个解决方案对你来说行不通 - 我会编辑我的答案来显示这一点。 - Jon Skeet
哎呀,你就差一点了!我肯定不是第一个遇到这个问题的人,一定有解决方法(或者解释为什么没有)的地方。 - Codesleuth

3
一种可能的解决方案:
class VeryBase {
    public VeryBase(string name) {
        Console.WriteLine(name);
    }
}

class Base<T> : VeryBase where T : Base<T> {
    protected Base()
        : base(typeof(T).Name) {
    }
}

class Derived : Base<Derived> {
    public Derived() {
    }
}

class Program {
    public static void Main(params string[] args) {
        Derived d = new Derived();
    }
}

抱歉,你是对的。这里应该使用this.GetType作为我的例子。然而,this未定义。 - Codesleuth
啊,好的...嗯,我已经更新了我的帖子并提供了一个可能的(即使不是很容易使用)解决方案... - Paolo Tedesco
如果您想添加另一层继承,并且将使用Type.Name的逻辑放入基类而不是派生类中,则变得更加棘手。(在问题中,该逻辑位于三个层次结构的中间层。) - Jon Skeet
@Jon:谢谢你的评论,我添加了额外的层。顺便说一下,我认为你的解决方案更好,这只是一个想法 :) - Paolo Tedesco
好的,现在你如何进一步从Derived派生?你也必须使它成为通用的...这是泛型普遍存在的问题 :( - Jon Skeet

3

如果在运行时确定继承类的类型比性能更重要,您可以查看堆栈跟踪以查看构造函数从哪里调用。在此示例中调用GetInheritedClassName时,您可能需要编写更多的假设:

public abstract class BaseClass : VeryBaseClass
{
    private static string GetInheritedClassName
    {
        get
        {
            // Get the current Stack
            StackTrace currentStack = new StackTrace();
            MethodBase method = currentStack.GetFrame(1).GetMethod();

            // 1st frame should be the constructor calling
            if (method.Name != ".ctor")
                return null;

            method = currentStack.GetFrame(2).GetMethod();

            // 2nd frame should be the constructor of the inherited class
            if (method.Name != ".ctor")
                return null;

            // return the type of the inherited class
            return method.ReflectedType.Name;
        }
    }

    public BaseClass(MyObject myObject) :
        base(GetInheritedClassName, myObject)
    {

    }
}

我无法想到一种不影响性能的动态获取继承类名称的好方法。我知道这是你想要避免的,但如果我需要调用一个有这些要求的第三方程序集,我会要求每个类指定自己的类型:

public abstract class BaseClass : VeryBaseClass
{
    public BaseClass(string className, MyObject myObject) :
        base(className, myObject)
    {

    }
}

public class InheritedClass : BaseClass
{
    public InheritedClass(MyObject myObject) : base("InheritedClass", myObject)
    {

    }
}

我喜欢这个答案的机智,但你说得对,它会影响性能。经过审查我的代码,我可以看到在任何时候我都不会一次构建许多这些对象,所以这绝对可行。绝对是一个+1的答案 :) - Codesleuth

3

GetType().Name不起作用吗?


2
不行,因为它隐式地使用了“this”,而你不能使用它来评估构造函数链接参数。 - Jon Skeet
抱歉,我不是指在构造函数链中,而是指VeryBaseClass的构造函数可以使用GetType().Name而不是期望它被传递。 - pdr
@pdr:这假设VeryBaseClass总是想要当前类型的名称;虽然在某些情况下这可能是合理的设计,但并不总是如此。我将其视为一个更广泛的问题,“当您想在构造函数链参数中使用this时该怎么办”——因为您并不总是能够将功能推到继承树上方。 - Jon Skeet
@pdr:我以前向他们抱怨过,但毫无效果。他们已经离开公司,而且已经在框架之上构建了一个应用程序。典型! - Codesleuth
@Codesleuth:我感同身受,真的。 - pdr
显示剩余3条评论

0

另一个选择...

// VeryBaseClass is in an external assembly
public abstract class VeryBaseClass
{
    public VeryBaseClass(string className)
    {
        Console.WriteLine(className);
    }
}

// BaseClass and InheritedClass are in my assembly
public abstract class BaseClass : VeryBaseClass
{
    public BaseClass(string className)
        :
        base(className)
    {

    }
}

public class InheritedClass : BaseClass
{
    public InheritedClass()
        : base(typeof(InheritedClass).Name)
    {
    }
}

这与将类名作为字符串输入是一样的。我想避免从InheritedClass传递任何不必要的内容到BaseClass构造函数中。 - Codesleuth

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