如何在泛型类中访问类型为T的静态属性?

本文关键字:静态 属性 访问类型 泛型类 | 更新日期: 2023-09-27 18:06:48

我试图完成以下场景,即通用TestClassWrapper将能够访问它所组成的类的静态属性(它们都将派生自TestClass)。比如:

public class TestClass
{
    public static int x = 5;
}
public class TestClassWrapper<T> where T : TestClass
{
    public int test()
    {
        return T.x;
    }
}

给出错误:

'T'是一个'类型参数',在给定的上下文中是无效的。

有什么建议吗?

如何在泛型类中访问类型为T的静态属性?

你基本上不能,至少不能没有反射。

一个选择是在构造函数中放置一个委托,这样创建实例的人就可以指定如何访问它:

var wrapper = new TestClassWrapper<TestClass>(() => TestClass.x);

如果有必要,你可以用反射来做:

public class TestClassWrapper<T> where T : TestClass
{
    private static readonly FieldInfo field = typeof(T).GetField("x");
    public int test()
    {
        return (int) field.GetValue(null);
    }
}

(必要时添加适当的绑定标志)

这不是很好,但至少您只需要查找一次字段…

你当然可以这样写:

public int test() 
{ 
    return TestClass.x; 
} 

即使在一个重要的例子中,你也不能覆盖一个静态字段,所以你总是会从你已知的基类中调用它。

泛型不支持与静态成员相关的任何内容,因此这不起作用。我的建议是:不要让它静止不动。假设该字段确实与特定的T相关,您还可以使用反射:

return (int) typeof(T).GetField("x").GetValue(null);

但我不建议这样做

为什么不直接返回TestClass.x呢?

另一个解决方案是简单地而不是使其成为静态的,并使用T上的new()约束来实例化对象。然后,您可以使用接口,并且包装器可以从实现该接口的任何类中获取该属性:

public interface XExposer
{
    Int32 X { get; }
}
public class TestClass : XExposer
{
    public Int32 X { get { return 5;} }
}
public class XExposerWrapper<T> where T : XExposer, new()
{
    public Int32 X
    {
        get { return new T().X; }
    }
}

事实上,您可以将其更改为TestClassWrapper上的public static Int32 X,并简单地将其取出为Int32 fetchedX = XExposerWrapper<TestClass>.X;

尽管由于调用this的任何代码都必须给参数T这些相同的约束,包装器类在这一点上是相当不必要的,因为调用代码本身也可以只执行new T().X,而不需要包装器。

仍然存在一些有趣的继承模型,其中这种结构很有用。例如,abstract class SuperClass<T> where T : SuperClass<T>, new()可以在其静态函数中实例化并返回类型T,从而有效地允许您创建适合子类的可继承静态函数(然后需要将其定义为class ChildClass : SuperClass<ChildClass>)。通过在超类上定义protected abstract函数/属性,您可以使函数在任何继承对象上应用相同的逻辑,但根据这些抽象的实现对该子类进行定制。我将此用于数据库类,其中表名和获取查询由子类实现。因为属性是受保护的,所以它们也永远不会暴露。

例如,在数据库类中,实际的抓取逻辑放在一个中心抽象类中:

public abstract class DbClass<T> where T : DbClass<T>, new()
{
    protected abstract String FetchQuery { get; }
    protected abstract void Initialize(DatabaseRecord row);
    public static T FetchObject(DatabaseSession dbSession, Int32 key)
    {
        T obj = new T();
        DatabaseRecord record = dbSession.RetrieveRecord(obj.FetchQuery, key);
        obj.Initialize(record);
        return obj;
    }
}

和实现:

public class User : DbClass<User>
{
    public Int32 Key { get; private set;}
    public String FirstName { get; set;}
    public String LastName { get; set;}
    protected override String FetchQuery
    { get { return "SELECT * FROM USER WHERE KEY = {0}";} }
    protected override void Initialize(DatabaseRecord row)
    {
        this.Key = DbTools.SafeGetInt(row.GetField("KEY"));
        this.FirstName = DbTools.SafeGetString(row.GetField("FIRST_NAME"));
        this.LastName = DbTools.SafeGetString(row.GetField("LAST_NAME"));
    }
}

可以这样使用:

User usr = User.FetchObject(dbSession, userKey);

这是一个相当简化的示例,但是正如您所看到的,该系统允许在子类上调用父类中的静态函数,以返回子类的对象。

T是一个类型,不是参数或变量,因此不能从任何成员中选择任何值。下面是一个示例代码:

public class UrlRecordService
{
    public virtual void SaveSlug<T>(T entity) where T : ISlugSupport
    {
        if (entity == null)
            throw new ArgumentNullException("entity");
        int entityId = entity.Id;
        string entityName = typeof(T).Name;
    }
}

public interface ISlugSupport
{
    int Id { get; set; }
}

cjk和Haris Hasan对问题的回答是最正确的。然而,在这个评论中,OP暗示他在追求c#中不太可能实现的其他东西:为派生类中的静态成员定义契约的方法。

没有严格定义的方法,但可以设置一个可能由基类(或接口)隐含的模式;例如:

public class TestClass
{
    private static int x;
    public virtual int StaticX => x;
}

或者如果不打算直接使用

public abstract class AbstractTestClass
{
    public abstract int StaticX {get;}
}

(在这个人为的例子中我更喜欢)
public interface ITest
{
    int StaticX {get;}
}

在其他地方,StaticXxx成员的这种模式可能(松散地)与应该用静态字段支持成员的实现相关联(如上面的TestClass)。

有趣的是,这个可以被泛型包装器(重新)暴露为静态,因为泛型静态与使用的每种类型是隔离的。
public class TestClassWrapper<T> where T : ITest, new()
{
    private readonly static T testInstance = new T();
    public static int test() => testInstance.x;
}

使用new()条件,但也可以使用创建ITest(或TestClassAbstractTestClass)实例的相关静态通用工厂模式。但是,如果不能拥有类的长寿命实例,则这可能不可行。

在这种情况下,您假设T是TestClass的一个子类。TestClass的子类不会有静态int x.