C#中静态方法的模板

3

我正在编写一个应用程序,允许用户运行测试。测试包括许多不同的对象,例如配置、温度和基准测试。设置等内容在xml之间保存。在我的代码中,我传递不同的XElements,以便可以根据不同情况构建最终的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
    }
}

[编辑]:修正问题(更改以上函数的签名)[/编辑]

当然,我实际上是不被允许重写BaseClass的静态方法的。有什么最好的方法来解决这个问题呢?我希望尽可能多地使以下代码有效:

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

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

[编辑]修改了上面的意图使用代码[/编辑]

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

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

1
为什么方法必须是静态的? - BoltClock
我希望能够保存一份配置列表,而不必创建一个配置实例。虽然这是可能的,但似乎并不应该是必要的。 - AGuyInAPlace
3个回答

4

静态方法不是类实例的一部分,因此覆盖它们毫无意义。它们无法访问作为成员的任何实例的非静态部分。

这有点像策略模式的场景,例如,您可以只使用单个静态的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等中也是如此吗?我偶尔会看到这样的命名方式,很好奇为什么要这样做。 - AGuyInAPlace
这样,你就不能使用 BaseClass.Save(data),你必须指定 data 的具体类型,这正是 OP 想要避免的。你可以通过使用类型推断来解决这个问题,但这需要列表为例如 List<Temperature>,而这似乎并不是情况。 - svick
这不太可行,因为它要求我返回一个 List<IBaseClass>,而我需要返回一个 List<Temperature>。否则,我无法访问 Temperature 特有的其他属性和方法。是否有一种方式可以使用 List<Temperature> Load() 替代 List<IBaseClass> Load()? - AGuyInAPlace
1
List<IBaseClass>List<Temperature> 是协变的。您可以创建一个派生对象模型,允许每个子类返回一个强类型列表,但您的目标是调用一个统一的方法,无论对象类型如何,因此它应该返回基本类型的列表。你不能两全其美。如果您将一个对象视为其最不派生的类型,则只能访问公共方法。 - Jamie Treworgy
@jamietre,List<T> 绝对不是协变的。 - svick
显示剩余6条评论

1

如果您不太关心保存格式,可以自由使用序列化(它在内部使用反射)。

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解析。 - AGuyInAPlace

1

如果共享的部分相同,您可以将其放在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)是一个虚方法,每个类型都要实现它。
显然,你不能用加载来做到这一点,你要么必须知道你正在加载的类型,要么有某种找出你正在加载的类型的方法。

这个问题涉及到处理对象列表的方法。我的假设是,不同的实现需要对列表进行更多操作而不仅仅是保存每个元素。否则,这个问题就是琐碎的,而且首先拥有这样一个静态方法几乎没有任何意义。 - Jamie Treworgy
正确。信息是从整个列表中收集的,例如最高和最低温度,或者预期时间量来运行基准测试列表。这些信息是特定于情况的,这意味着每个派生类都需要save()和load()的独特实现。 - AGuyInAPlace

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接