【问题标题】:A Template for Static Methods in C#C# 中的静态方法模板
【发布时间】:2011-10-20 08:40:24
【问题描述】:

我正在编写一个允许用户运行测试的应用程序。测试由许多不同的对象组成,例如配置、温度和基准。设置之类的东西在xml之间来回保存。我在代码中传递了不同的 XElement,因此我可以针对不同的情况以不同的方式构建最终的 xml 文档。我想做这样的事情:

public abstract class BaseClass<T>
{
    abstract static XElement Save(List<T>);
    abstract static List<T> Load(XElement structure);
}

public class Configuration : BaseClass<Configuration>
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    //etc...

    public static XElement Save(List<Configuration>)
    {
        XElement xRoot = new XElement("Root");
        //etc...
        return xRoot;
    }

    public static List<Configuration> Load(XElement structure)
    {
        List<BaseClass> list = new List<BaseClass>();
        //etc...
        return list;
    }
}

public class Temperature : BaseClass<Temperature>
{
    public float Value { get; set; }

    public static XElement Save(List<Temperature>)
    {
        //save
    }

    public static List<Temperature> Load(XElement structure)
    {
        //load
    }
}

[EDIT]:修改问题(更改上述函数的签名)[/EDIT]

当然,我实际上是不允许重写 BaseClass 的静态方法的。解决这个问题的最佳方法是什么?我希望以下内容尽可能有效:

List<Temperature> mTemps = Temperature.Load(element);
List<Configuration> mConfigs = Configuration.Load(element);

Temperature.Save(mTemps);
Configuration.Save(mConfigs);

[EDIT]更改了上面的预期使用代码[/EDIT]

我能想到的唯一解决方案是以下,这是不可接受的:

public class File
{
    public static XElement Save(List<Temperature> temps)
    {
        //save temp.Value
    }

    public static XElement Save(List<Configuration> configs)
    {
        //save config.Property1
        //save config.Property2
    }

    //etc...
}

【问题讨论】:

  • 为什么方法必须是静态的?
  • 我希望能够保存配置列表,而无需创建配置实例。虽然这是可能的,但似乎没有必要。

标签: c# inheritance methods static


【解决方案1】:

静态方法不是类实例的一部分。所以无论如何覆盖它们没有任何意义。他们不能访问他们碰巧是其成员的实例的任何非静态部分。

这是一种策略模式场景,例如您可以只使用单个静态 Load & Save 方法来检查传递给它们的对象的类型,并采取相应的行动。但这里有另一种更巧妙的方法,它使用泛型类型创建原型并调用其方法,允许您将逻辑保留在每个派生对象类型中。

(再次编辑)

这是另一个破解方法,与我最初的建议相同。我实际上对此进行了测试并且它有效,所以我认为这是你可以做的最好的事情来获得你正在寻找的所有功能(除了测试类型和有条件地调用代码)。您仍然需要为Load 传递一个类型,否则,运行时将不知道预期的返回类型。但是Save 普遍适用。并且子类实现是强类型的。

这只是使用列表中的第一个对象作为它的原型,很简单。

public interface IBaseObject 
{
    XmlElement Save(IEnumerable<IBaseObject> list);
    IEnumerable<IBaseObject> Load(XmlElement element);
}
public interface IBaseObject<T> where T: IBaseObject 
{
    XmlElement Save(IEnumerable<T> list);
    IEnumerable<T> Load(XmlElement element);
}

public class Temperature : IBaseObject<Temperature>, IBaseObject 
{

    public XmlElement Save(IEnumerable<Temperature> list)
    {
        throw new NotImplementedException("Save in Temperature was called");
    }

    public IEnumerable<Temperature> Load(XmlElement element)
    {
        throw new NotImplementedException("Load in Temperature was called");
    }

    // must implement the nongeneric interface explicitly as well

    XmlElement IBaseObject.Save(IEnumerable<IBaseObject> list)
    {
        return Save((IEnumerable<Temperature>)list);
    }

    IEnumerable<IBaseObject> IBaseObject.Load(XmlElement element)
    {
        return Load(element);
    }
}

// or whatever class you want your static methods living in

public class BaseObjectFile
{
    public static XmlElement Save(IEnumerable<IBaseObject> list)
    {
        IBaseObject obj = list.DefaultIfEmpty(null).First();  // linq
        return obj==null ? null : obj.Save(list);
    }
    public static IEnumerable<IBaseObject> Load<T>(XmlElement element) 
        where T: IBaseObject, new()
    {
        IBaseObject proto = new T();
        return proto.Load(element);
    }
}

(原始编辑)

这有一个问题,您必须使用类型调用静态方法,例如

BaseClass<Temperature>.Load()

Save 方法有一种解决方法,但您想要的部分内容是不可能的。 Load 方法不知道要返回什么类型的列表,因为它的唯一参数没有关于返回类型的信息。因此,它不可能决定创建哪种类型作为原型。所以无论如何,如果你想使用普通的 Load 方法,你必须像上面的语法那样传递一个类型。

对于Save方法,你可以使用反射在静态方法中创建原型,从第一个元素中获取类型,然后从原型中调用Save方法。因此,如果您只需要随心所欲地使用 Save 方法,那就可以了。

不过,我认为最终做这样的事情会简单得多:

public static XElement Save(List<IBaseClass> list)
{       
    if (list is Temperature) {
       // do temperature code
    } else if (list is SomethingElse) {
      // do something else
    } 
}

无论如何 - 就像我说的那样,它需要反思才能使 Save 方法以这种方式工作。我只是使用简单的方法。

(删除原始错误代码)

【讨论】:

  • 另外,不相关但以下划线'_'开头的方法名称有什么意义吗?即使是其他语言,如 C++、Java 等?这是我不时看到的东西,我很好奇为什么会这样做。
  • 这样,你不能做BaseClass.Save(data),你必须指定data的具体类型,这正是OP试图避免的。您可以通过使用类型推断来解决这个问题,但这需要列表是例如List&lt;Temperature&gt;,好像不是这样的。
  • 这是一种约定,有时用于内部使用的方法。这实际上是我们的一个问题 - 你想要的部分是不可能的,我即将更新答案。
  • 这不太行,因为当我需要返回 List 时,它需要我返回 List。否则,我无法访问温度独有的其他属性和方法。有没有办法使用 List Load() 而不是 List Load()?
  • List&lt;IBaseClass&gt;List&lt;Temperature&gt; 是协变的。您可以创建一个派生对象模型,允许每个子类返回一个强类型列表,但您的目标是调用一个统一的方法,无论对象类型是什么。因此它应该返回一个基本类型的列表。你不能两全其美。如果您将对象视为其最小派生类型,则只能访问常用方法。
【解决方案2】:

如果您并不真正关心其保存的格式,您可以随意使用序列化(在内部使用反射)。

string SerialiseToString<T>(T source)
{
    using (StringWriter sw = new StringWriter() && XmlSerializer xml = new XmlSerializer(typeof(OrderedItem)))
    {
        xml.Serializer(sw, source);
        return sw.ToString();
    }
}

如果您想将其合并到 XML 文件的较大部分中,最简单的方法是解析此输出并将其添加到您的输出中。或者,您可以自己反映属性。

【讨论】:

  • 恐怕目的是根据我目前使用的 BaseClass 类型为 save() 和 load() 函数提供一个独特的实现。每种类型都有必须发送到/从 xml 解析的附加信息。
【解决方案3】:

如果共享部分相同,可以放在BaseClass

public static XElement Save(IEnumerable<BaseClass> list)
{
    var root = new XElement("root");
    foreach (var item in list)
    {
        item.Save(root);
    }
    return root;
}

这里Save(XElement)是一个虚方法,每个类型都实现它。

显然,你不能通过加载来做到这一点,你要么必须知道你正在加载什么类型,要么有一些方法可以找出你正在加载的类型。

【讨论】:

  • 问题是关于处理对象列表的方法。我的假设是不同的实现需要对列表做更多的事情,而不仅仅是保存每个元素。否则这个问题是微不足道的,一开始就有这样的静态方法没有什么意义。
  • 正确。信息是从整个列表中收集的,例如最高和最低温度,或运行基准测试列表的预期时间量。此信息是特定于案例的,这意味着每个派生类都需要一个独特的 save() 和 load() 实现
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-10
  • 1970-01-01
相关资源
最近更新 更多