【问题标题】:Generic Class Polymorphisim泛型类多态
【发布时间】:2017-08-20 02:24:42
【问题描述】:

如果我有以下情况:

public abstract class Parameter<T>  
{
    protected T value;

    public virtual T Value
    {
        get { return value; }
        set { this.value = value; }
    }

    protected Parameter(T startingValue)
    {
        value = startingValue;
    }
}

public class FloatParameter : Parameter<float> 
{
    public FloatParameter(float startingValue) : base(startingValue){}
}

public class IntParameter : Parameter<int> 
{
    public override int Value
    {
        get { return value; }
        set { this.value = value > 100 ? 100 : value; }
    }

    public IntParameter(int startingValue) : base (startingValue) {}
}

有没有办法创建一些可以包含任何派生类型的List&lt;Parameter&gt;?例如,类似:

// no type specified in Parameter
List<Parameter> storedParameters = new List<Parameter>(); 
storedParameters.Add(new FloatParameter(2f));
storedParameters.Add(new IntParameter(7));

foreach(Parameter p in storedParameters)
{
    DoSomethingWithValue(p.Value);
}

或者,或者,如果这个实现有缺陷,有没有更好的方法来做到这一点?我在这里的感觉有点幼稚。

【问题讨论】:

  • 为什么要将芒果放在橙子列表中?
  • 那么,Value 在您的示例中应该有什么类型?
  • 这个实现的问题是你尝试使用的两种类型。 Int 和 float 是结构,这意味着一旦将 T 分配给其中一个,就不能再添加另一个。
  • 你能添加DoSomethingWithValue的代码吗?
  • DoSomethingWithValue 是一种对值进行运行时类型检查的通用方法。并且Value 应该与泛型指定的类型相同。所以FloatParameterValue 属性应该返回一个浮点数。

标签: c# .net oop generics polymorphism


【解决方案1】:

我认为管理这种情况的唯一方法是拥有用于管理泛型类型的接口,这样的东西应该可以工作:

public interface IParameter
{
    void DoSomething();
}

public abstract class Parameter<T>
{
    protected T value;

    public T Value
    {
        get { return value; }
        set { this.value = value; }
    }

    protected Parameter(T startingValue)
    {
        value = startingValue;
    }
}

public class FloatParameter : Parameter<float>, IParameter
{
    public FloatParameter(float startingValue) : base(startingValue) { }
    public void DoSomething()
    {
        Console.WriteLine(value);
    }
}

public class IntParameter : Parameter<int>, IParameter
{
    public IntParameter(int startingValue) : base(startingValue) { }

    public void DoSomething()
    {
        Console.WriteLine(value);
    }
}

在他的情况下,您将能够创建接口 IParameter 的列表并在那里添加特定实例:

 var list = new List<IParameter>();
 list.Add(new FloatParameter(1F));
 list.Add(new IntParameter(1));

 foreach (var item in list)
 {
      item.DoSomething();
 }

【讨论】:

  • 这是一个非常好的方法。但是,您能想出一种方法让我从item foreach 变量中访问Parameter 对象的属性吗?
  • @DannyHerbert 那是不可能的。编译器无法知道列表中存储了哪些特定类型(实际上,这可能取决于在程序运行之前不可用的用户输入)。因此,它无法提供对更具体类型的属性的访问,因为它不知道它们实际上会存在。
【解决方案2】:

尝试添加非泛型接口。这是一个例子:

public class Program
{
    static void Main(string[] args)
    {          
        try
        {
            List<IParameter> storedParameters = new List<IParameter>();
            storedParameters.Add(new FloatParameter(2f));
            storedParameters.Add(new IntParameter(7));

            foreach (IParameter p in storedParameters)
            {
                Console.WriteLine(p.ToString());
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

public interface IParameter
{
    object value { get; }
}

public class Parameter<T> : IParameter
{
    public object value { get; protected set; }

    public virtual T Value
    {
        get { return (T)value; }
        set { this.value = value; }
    }


    protected Parameter(T startingValue)
    {
        value = startingValue;
    }
}

public class FloatParameter : Parameter<float>
{
    public FloatParameter(float startingValue) : base(startingValue){ }
}

public class IntParameter : Parameter<int>
{
    public override int Value
    {
        get { return (int)value; }
        set { this.value = value > 100 ? 100 : value; }
    }

    public IntParameter(int startingValue) : base (startingValue) { }
}

【讨论】:

  • object 字段中存储原始T 将需要在通过T Value 属性访问时进行装箱/拆箱。此外,如果显式实现 IParameter,则可以使用相同的属性名称。
【解决方案3】:

不,这是不可能的。

您要做的是拥有一个接口(或基类),该接口(或基类)公开未定义类型的属性,然后能够检索该值并将其动态分派给DoSomethingWithValue的正确覆盖。

您所追求的可以将属性定义为dynamic,而不是使用泛型。

public class Parameter
{
    protected dynamic value;

    public dynamic Value
    {
        get { return value; }
        set { this.value = value; }
    }

    public Parameter(dynamic startingValue)
    {
        value = startingValue;
    }
}

public class MyStuff {
    public void DoStuff()
    {
        List<Parameter> storedParameters = new List<Parameter>();
        storedParameters.Add(new Parameter(2f));
        storedParameters.Add(new Parameter(7));

        foreach (Parameter p in storedParameters)
        {
            DoSomethingWithValue(p.Value);
        }
    }
}

否则,您应该考虑实施Double dispatch

【讨论】:

    【解决方案4】:

    您可以通过定义一个通用接口并使用访问者模式来做到这一点。

    public interface IParameterVisitor
    {
        void VisitInt(int value);
        void VisitFloat(float value);
    }
    
    public interface IParameter
    {
        void Accept(IParameterVisitor visitor);
    }
    

    之前的实现需要稍微修改一下:

    public abstract class Parameter<T> : IParameter
    {
        protected T value;
    
        public virtual T Value
        {
            get { return value; }
            set { this.value = value; }
        }
    
        protected Parameter(T startingValue)
        {
            value = startingValue;
        }
    
        public abstract void Accept(IParameterVisitor visitor);
    }
    

    FloatParameterVisitFloatIntParameterVisitInt

    public class FloatParameter : Parameter<float>
    {
        public FloatParameter(float startingValue) : base(startingValue) { }
        public override void Accept(IParameterVisitor visitor)
        {
            visitor.VisitFloat(this.value);
        }
    }
    
    public class IntParameter : Parameter<int>
    {
        public override int Value
        {
            get { return value; }
            set { this.value = value > 100 ? 100 : value; }
        }
    
        public override void Accept(IParameterVisitor visitor)
        {
            visitor.VisitInt(this.value);
        }
    
        public IntParameter(int startingValue) : base(startingValue) { }
    }
    

    例如我们的访客:

    public class MyVisitor : IParameterVisitor
    {
        public void VisitInt(int value)
        {
            Console.WriteLine($"Visiting an int: {value}");
        }
    
        public void VisitFloat(float value)
        {
            Console.WriteLine($"Visiting a float: {value}");
        }
    }
    

    最后,用法:

    var parameters = 
        new List<IParameter> {new FloatParameter(0.5f), new IntParameter(1)};
    var visitor = new MyVisitor();
    foreach (IParameter parameter in parameters) {
       parameter.Accept(visitor);
    }
    

    【讨论】:

      【解决方案5】:

      如果您将值更改为对象,您将能够将值设置为您喜欢的任何类型:

      class Program
      {
          static void Main(string[] args)
          {
      
              // no type specified in Parameter
              var storedParameters = new List<ParameterBase>();
              storedParameters.Add(new FloatParameter(3.5F));
              storedParameters.Add(new IntParameter(7));
      
              foreach (var p in storedParameters)
              {
                  Console.WriteLine(p.Value);
              }
          }
      }
      
      public class ParameterBase
      {
          protected object value;
      
          public virtual object Value
          {
              get { return value; }
              set { this.value = value; }
          }
      }
      
      public class FloatParameter : ParameterBase
      {
          public FloatParameter(float value)
          {
              Value = value;
          }
      }
      
      public class IntParameter : ParameterBase
      {
          public IntParameter(int value)
          {
              Value = value;
          }
      }
      

      更新:@Pieter Witvoet 建议使用对象而不是动态和删除的 ValueType

      【讨论】:

      • 你为什么不用objectdynamic 主要用于动态语言互操作,并附带额外费用(后期绑定)。
      • 感谢@Pieter Witvoet,我已经更新了使用对象的代码:)
      • 另一件事:为什么必须单独设置类型?这很容易被滥用:Value = "hello"; ValueType = typeof(int);。但为什么要存储它呢? IntParameter 应该总是返回typeof(int),那么为什么不将基类属性抽象化,并在每个派生的Parameter 类中重写它呢?请注意 IntParameterFloatParameter 之间的区别非常小 - 您可以使用单个通用的 Parameter&lt;T&gt; 类代替(当然,ValueType 返回 typeof(T))。
      • 这里是为了说明设置的类型是 int 或 float 类型。
      猜你喜欢
      • 2011-07-10
      • 2023-03-05
      • 1970-01-01
      • 1970-01-01
      • 2012-12-10
      • 2013-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多