简体   繁体   中英

C# Calling a Method on an Unknown Generic Type

I have a base class "ManagerBase" that has a generic static function "GetManager. There are two classes that inherit from ManagerBase ("ManagerSomething1" and "ManagerSomething2"). Each class has a static function "GetManager" which returns the inherited type of ManagerBase. How would I call GetManager without knowing the type?

public class ManagerBase
{
    public static T GetManager<T>(int managerID, bool withUsers, System.Func<SqlDataReader, T> del) where T : ManagerBase
    {
        T manager = del(dr);
        return manager;
    }
}

public class ManagerSomething1 : ManagerBase
{
    public static ManagerSomething1 GetManager<ManagerSomething1>(int managerID, bool withUsers)
    {
        return ManagerBase.GetManager<ManagerSomething1>(managerID, withUsers, dr => new ManagerSomething1(dr));
    }
}

public class ManagerSomething2 : ManagerBase
{
    public static ManagerSomething2 GetManager<ManagerSomething2>(int managerID, bool withUsers)
    {
        return ManagerBase.GetManager<ManagerSomething2>(managerID, withUsers, dr => new ManagerSomething2(dr));
    }
}

public static class SessionSharedHelper<T> where T : ManagerBase
{
    public static void InitializeSession(int managerID, bool withUsers)
    {
        SessionShared<T>.Manager = //I don't know how I can call ManagerBase.GetManager<T>(managerID, withUsers, ...);
    }
}

You could refactor to something like this:

    public abstract class ManagerBase
    {
        public ManagerBase() { }

        public abstract void Initialize(int managerID, bool withUsers);
    }

    public class ManagerSomething1 : ManagerBase
    {
        public ManagerSomething1() 
        { }

        public override void Initialize(int managerID, bool withUsers)
        {
        }
    }

    public class ManagerSomething2 : ManagerBase
    {
        public ManagerSomething2()
        {
        }

        public override void Initialize(int managerID, bool withUsers)
        {
            throw new NotImplementedException();
        }
    }

    public static class SessionSharedHelper<T> where T : ManagerBase, new()
    {
        public static void InitializeSession(int managerID, bool withUsers)
        {
            T manager = new T();
            manager.Initialize(managerID, withUsers);
        }
    }

Something like this might work:

MethodInfo method_info = typeof(T).GetMethod("GetManager",
   System.Reflection.BindingFlags.Static | BindingFlags.Public);
SessionShared<T>.Manager = 
   (T)method_info.Invoke(null, new object[]{managerID, withUsers, ...});

I think you're trying to implement a factory pattern of some sort, inside of the classes you're trying to construct.

There are two ways you could go: Have a separate class that knows how to construct Manager1 and Manager2, or have the base class know how to construct each of its children. Either way, something needs to have knowledge of each of the subclasses. I don't think there's a good way to do what you have without reflection.

I'd probably implement something like this in a separate factory class.

public static T GetManager<T>(int managerID, bool withUsers) where T : ManagerBase
{
    if (typeof(T) == typeof(Manager1))
    {
        return new Manager1(managerID, withUsers) as T;
    }
    if (typeof(T) == typeof(Manager2))
    {
        return new Manager2(managerID, withUsers) as T;
    }

    throw new ArgumentException();
}

Something somewhere has to know/decide which type of Manager class to create when you say "give me the Manager* object for managerID x." So one question that needs to be answered is how that decision is made. Is that determined by some kind of data that comes back with the data reader?

Rather than having "GetManager" static methods, you could create a manager Factory or Repository class that understands how to determine which type of Manager object to create given a data reader.

Below is an example implementation. The idea is that at some point early on, such as when your application is starting, you create a ManagerRepository and then register a "create" delegate for each type of Manager class that you have. Later, when you request a Manager object from the ManagerRepository it will decide which type of Manager class to return and will utilize the "create" delegate that you registered for that type.

public class ManagerBase
{
}

class ManagerRepository
{
    private Dictionary<Type, Func<SqlDataReader, ManagerBase>> _ManagerCreateDelegates;

    public ManagerRepository()
    {
        _ManagerCreateDelegates = new Dictionary<Type, Func<SqlDataReader, ManagerBase>>();
    }

    public void RegisterCreate<T>(Func<SqlDataReader, ManagerBase> create)
        where T : ManagerBase
    {
        _ManagerCreateDelegates[typeof(T)] = create;
    }

    public ManagerBase GetManager(int managerID, bool withUsers)
    {
        SqlDataReader reader;
        reader = null;// TODO: obtain a data reader from somewhere...

        Type typeOfManager = this.DetermineManagerType(reader);

        Func<SqlDataReader, ManagerBase> create;
        if (_ManagerCreateDelegates.TryGetValue(typeOfManager, out create))
        {
            return create(reader);
        }
        else
        {
            throw new InvalidOperationException(string.Format("No create delegate has been registered for type [{0}].", typeOfManager.FullName));
        }
    }

    private Type DetermineManagerType(SqlDataReader reader)
    {
        // TODO: implement logic that uses the data reader to decide which type of Manager object to create
        throw new NotImplementedException();
    }
}

public class ManagerSomething1 : ManagerBase
{
    public ManagerSomething1(SqlDataReader reader)
    {
        // TODO: implement logic to construct ManagerSomething1 given a data reader
    }
}

public class ManagerSomething2 : ManagerBase
{
    public ManagerSomething2(SqlDataReader reader)
    {
        // TODO: implement logic to construct ManagerSomething1 given a data reader
    }
}

class Program
{
    static void Main(string[] args)
    {
        // create a ManagerRepository somewhere
        ManagerRepository repository = new ManagerRepository();

        // register create delegates for each type of Manager
        repository.RegisterCreate<ManagerSomething1>(dr => new ManagerSomething1(dr));
        repository.RegisterCreate<ManagerSomething2>(dr => new ManagerSomething2(dr));

        // use the repository
        int managerID = 5;
        bool withUsers = false;
        ManagerBase manager = repository.GetManager(managerID, withUsers);
    }
}

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