简体   繁体   中英

How can I access a static property of type T in a generic class?

I am trying to accomplish the following scenario that the generic TestClassWrapper will be able to access static properties of classes it is made of (they will all derive from TestClass). Something like:

public class TestClass
{
    public static int x = 5;
}

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

Gives the error:

'T' is a 'type parameter', which is not valid in the given context.

Any suggestions?

You can't, basically, at least not without reflection.

One option is to put a delegate in your constructor so that whoever creates an instance can specify how to get at it:

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

You could do it with reflection if necessary:

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

    public int test()
    {
        return (int) field.GetValue(null);
    }
}

(Add appropriate binding flags if necessary.)

This isn't great, but at least you only need to look up the field once...

Surely you can just write this:

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

Even in a nontrivial example, you can't override a static field so will always call it from your known base class.

为什么不直接返回TestClass.x

T is a type, not parameter or variable so you cannot pick any value from any members. Here is a sample code.

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

Another solution is to simply not make it static, and work with the new() constraint on T to instantiate the object. Then you can work with an interface, and the wrapper can get the property out of any class that implements that interface:

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

In fact, you can change that to public static Int32 X on the TestClassWrapper and simply get it out as Int32 fetchedX = XExposerWrapper<TestClass>.X;

Though since whatever code calls this will have to give the parameter T those same constraints, the wrapper class is pretty unnecessary at this point, since that calling code itself could also just execute new T().X and not bother with the wrapper.

Still, there are some interesting inheritance models where this kind of structure is useful. For example, an abstract class SuperClass<T> where T : SuperClass<T>, new() can both instantiate and return type T in its static functions, effectively allowing you to make inheritable static functions that adapt to the child classes (which would then need to be defined as class ChildClass : SuperClass<ChildClass> ). By defining protected abstract functions / properties on the superclass, you can make functions that apply the same logic on any inherited object, but customized to that subclass according to its implementations of these abstracts. I use this for database classes where the table name and fetch query are implemented by the child class. Since the properties are protected, they are never exposed, either.

For example, on database classes, where the actual fetching logic is put in one central abstract class:

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(row);
        return obj;
    }
}

And the implementation:

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 = DatabaseSession.SafeGetInt(row.GetField("KEY"));
        this.FirstName = DatabaseSession.SafeGetString(row.GetField("FIRST_NAME"));
        this.LastName = DatabaseSession.SafeGetString(row.GetField("LAST_NAME"));
    }
}

This can be used as:

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

This is a rather simplified example, but as you see, this system allows a static function from the parent class to be called on the child class, to return an object of the child class.

cjk and Haris Hasan have the most-correct answers to the question as asked. However in this comment the OP implies that he is after something else not quite possible in C#: a way to define a contract for a static member in a derived class.

There isn't a way to strictly define this, but it is possible to set up a pattern that may be implied by a base class (or interface); eg:

public class TestClass
{
    private static int x;

    public virtual int StaticX => x;
}

or if not intended to be used directly

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

or (my preference in this contrived example)

public interface ITest
{
    int StaticX {get;}
}

Elsewhere, this pattern of a StaticXxx member may be (loosely) associated with implementations that should back the member with static fields (as in TestClass above).

What's kind of fun is that this can be (re)exposed as static by the generic wrapper, because generic statics are isolated to each type used.

public class TestClassWrapper<T> where T : ITest, new()
{
    private readonly static T testInstance = new T();

    public static int test() => testInstance.x;
}

This uses a new() condition, but an associated static, generic factory pattern for creating ITest (or TestClass or AbstractTestClass ) instances may also be used. However this may not be feasible if you can't have long-lived instances of the class.

In this situation you assume that T is a subclass of TestClass. Subclasses of TestClass will not have the static int x.

Generics do not support anything related to static members, so that won't work. My advice would be: don't make it static. Assuming the field genuinely relates to the specific T , you could also use reflection:

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

but I don't recommend it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM