簡體   English   中英

如何從 C# 中的基礎 class 參考中調用派生 class 的通用方法

[英]How to call generic method of derived class from base class reference in C#

我有以下基礎 class(省略版本):

class BaseClass
{  
}

我創建了以下派生類:

class DataPreparationClass<T> : BaseClass
{
}

class DataClass<T,U> : DataPreparationClass<T>
{
  public virtual void Start<U>(U arg)
  {}
}

class DataClassMain : DataClass<InputData,Data>
{
  public override void Start(Data argument)
  {
    base.Start(argument);
  }
}

class DataClassAux : DataClass<InputData,AuxData>
{
  public override void Start(AuxData argument)
  {
    base.Start(argument);
  }
}

我有一個List<BaseClass>包含各種派生實例(有更多派生類型),我打算分別調用它們的Start方法:

List<BaseClass> instances = GetAllInstance();

foreach(BaseClass instance in instances)
{
  object arg = GetArgsForInstance(instance);
  // instance.Start(arg); //can't call this
}

但是,由於它們的共同基礎是BaseClass ,所以我不能在沒有強制轉換的情況下調用Start ......基本上所有可能的類型,因為它們的類型在處理時是未知的。

如果我使用動態

 ((dynamic)target).Start(new Data("starting")); //target is of type DataClassMain<InputData,Data>

我得到一個例外:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:'Client.DataClass<InputData,Data>.Start(Data)' 的最佳重載方法匹配有一些無效參數'

那么我應該如何調用未知方法呢?

我會說,您的問題表明您的 model 存在多個缺陷:

  • 根據您的類的定義,您的 Start() 方法之間沒有多態性: Start(Data) 不會覆蓋 Start<U>(U)
  • 根據您的 GetArgsForInstance() 方法的定義,您丟失了所需的類型信息。
  • 我要補充一點,稱為類的類和稱為數據的數據並以其內容進行參數化的類太通用了。

也就是說,您的問題意味着您不想修復這些缺陷,也許它們超出了您的控制范圍,因此您必須忍受它:

  • 而不是丟失類型信息,您要求一種方法來檢索它。
  • 而不是使用多態性從最佳位置檢索類型(在我看來),這是 Start 方法本身,您要求一種在調用代碼中檢索它的方法。

所以,我會嘗試做的是:

  1. 重做您的 GetArgsForInstance() 方法,以免丟失此信息,例如,將其替換為 object,例如:
        class DataClassMain : DataClass<InputData,Data>
        {
          public override void Start(ArgumentProvider argumentProvider)
          {
            Data argument = argumentProvider.getArgumentAsData(argumentProvider);
            base.Start(argument);
          }
        }
  1. 如果不可能,請從派生類內部檢索類型,例如:
        public class DataClassMain : DataClass<InputData,Data>
        {
            public override void Start(object arg)
            {
                base.Start(arg);
                Data argAsData = (Data) arg;
            }
        }
  1. 如果不可能,這意味着您已經有一組約束使您的代碼難以維護,所以讓我們使用 go 來處理一個凌亂的反射性事物,但是您必須意識到不涉及多態性並擺脫您的“覆蓋” ' 和 Start() 方法上的 'virtual' 修飾符。 這是一個完整的工作程序,output 是:
DataClassMain
DataClassAux
        public static void Main(string[] args)
        {
            List<BaseClass> instances = GetAllInstance();

            foreach(BaseClass instance in instances)
            {
                object value = GetArgsForInstance(instance);
                messyInvoke(instance, value);
            }
        }

        private static void messyInvoke(BaseClass instance, object value)
        {
            MethodInfo method = instance.GetType().GetMethod("Start");
            if (method != null)
            {
                ParameterInfo[] parametersInfos = method.GetParameters();
                if (parametersInfos.Length == 1)
                {
                    object[] paramArray = {value};
                    method.Invoke(instance, paramArray);
                }
            }
        }

        public class BaseClass{
            public virtual Type GetTypeOfArgs()
            {
                return typeof(Toto);
            }
        }
        public class DataPreparationClass<T> : BaseClass {}

        public abstract class DataClass<T> : DataPreparationClass<T>
        {
        }
        public class DataClassMain : DataClass<Toto>
        {
            public void Start(Data arg)
            {
                Console.WriteLine("DataClassMain");
            }
        }

        public class DataClassAux : DataClass<Toto>
        {
            public void Start(AuxData argument)
            {
                Console.WriteLine("DataClassAux");
            }
        }

        private static object GetArgsForInstance(BaseClass isntance)
        {
            if (isntance is DataClassMain)
                return new Data();
            if (isntance is DataClassAux)
                return new AuxData();
            throw new ArgumentException();
        }

        private static List<BaseClass> GetAllInstance()
        {
            return new List<BaseClass> {new DataClassMain(), new DataClassAux()};
        }

        
        public class Toto{}
        
        public class DataClassInputData
        {
        }
        public class Data : DataClassInputData
        {
        }
        public class AuxData : DataClassInputData
        {
        }

因此,對您的問題最直接的答案是使用模式匹配來調用 start 方法。

List<BaseClass> instances = GetAllInstance();
foreach(BaseClass instance in instances)
{
    object arg = GetArgsForInstance(instance);
    switch(instance){
        case DataClassMain d : d.Start((Data)arg); break;
        case DataClassAux a : a.Start((AuxData)arg);break;
        default: throw new Exception();
    }
}

但我確實覺得這是一個令人費解且不合適的 inheritance 鏈,您應該真正考慮使用工廠和/或策略模式。

假設GetArgsForInstance總是會返回關於它作為參數接收的類型的正確類型,並且返回類型(Data、AuxData 等)共享一個公共的基本類型。 所以我們可以直接使用T GetArgsForInstance<T>(BaseClass b)的簽名來進行類型解析。 這樣,您可以確保在返回之前獲得正確類型的 args。

由於Start覆蓋只是傳遞調用泛型類型,因此DataClassMainDataClassAux中的覆蓋是不必要的。

如果我們稍微修改一下 DataClass,我們可以這樣做:

class DataClass<T,U> : DataPreparationClass<T>
{
  public virtual void Start(U arg)
  {
      //Do somethin with arg
      
  }
  public void Call(Func<BaseClass,U> f){
     U data = f.Invoke(this);
     Start(data);
  }
}

並調用它

List<BaseClass> instances = GetAllInstance();

foreach(BaseClass instance in instances)
{
    switch(instance)
    {
        case DataClassMain d : d.Call(GetArgsForInstance<Data>); break;
        case DataClassAux a : a.Call(GetArgsForInstance<AuxData>);break;
        default: throw new Exception();
    }
}

這樣做更可取的原因是我們可以讓編譯器確保我們只將適當的類型傳遞給不同的方法,而不需要強制轉換。

但同樣,幾乎總是應該避免這種復雜的 inheritance 鏈。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM