【问题标题】:Can I override ToString() for classes in a public API?我可以为公共 API 中的类覆盖 ToString() 吗?
【发布时间】:2011-05-11 18:53:18
【问题描述】:

我正在为另一个软件编写一个与 API 交互的工具。我的工具的一部分需要生成关于通过 API 找到的各种对象的报告,我希望这些报告包含标识每个对象的简单字符串。默认情况下,我计划使用 ToString() 为每个对象生成字符串。但是,毫不奇怪,我发现此 API 中的默认 ToString() 实现不是描述性的。

最初我想用一个长的 Switch 语句来做类似下面的代码的事情。尽管这很可能会变得难以控制。

public string GetAPIObjectDescrition(object obj)
{
     Type t = obj.GetType();

     Switch(t)
     { 
         Case typeof(SomeAPIType):
             SomeAPIType x = (SomeAPIType)obj;
             return  x.SomeProperty;             


         Case typeof(SomeOtherAPIType):
             SomeOtherAPITypex = (SomeOtherAPIType)obj;
             return  x.SomeOtherProperty;

         default:
             return x.ToString();
     }
} 

接下来我尝试使用扩展方法(参见下面的代码)。 CustomObjectDescription() 按预期工作,但是当我尝试调用 ToString() 时,它只返回默认的 ToString() 结果。我以前从未使用过扩展方法,所以我可能会完全不以为然地认为这样的事情是可能的。

我不能保证 API 中遇到的每个类型都会有一个 CustomObjectDescription() 扩展,所以如果我采用这条路线,我最终将不得不每次都使用反射来检查当前对象是否具有 GetObjectDescription () 延期。如果可能的话,我想避免使用反射。

public static class APIObjectDescriptionExtensions
{
    public static string ToString(this APIObject element)
    {
        return "ElementName = " + element.Name + " ElementID =" + element.Id.IntegerValue.ToString();
    }

    public static string CustomObjectDescription(this APIObject element)
    {
        return "ElementName = " + element.Name + " ElementID =" + element.Id.IntegerValue.ToString();
    }
}

是否有人对我应该如何解决这个问题有任何其他建议?我更喜欢一种解决方案,其中每个 API 类型的代码相互独立(没有巨大的 Switch 语句)。

如果可能的话,我希望将一种类型的描述字符串代码继承到子类型,除非这些类型有自己唯一的描述字符串代码。

我认为可能有更好的解决方案,包括创建自定义 TypeConverters 或覆盖/扩展 System.Convert.ToString()?


更新

我认为下面的示例可能有助于阐明我想要做什么。最终,我希望能够从此 API 中获取任意类,直到运行时才知道其类型,并生成描述字符串。如果 Type 有我的自定义扩展方法,那么应该使用它,否则代码应该使用普通的旧 ToString()。

    public static string GetDataDescription(object o)
    {
        //get the type of the input object
        Type objectType = o.GetType();

        //check to see if a description extension method is defined
        System.Reflection.MethodInfo extensionMethod = objectType.GetMethod("MyDescriptionExtensionMethod");

        if (extensionMethod != null)
        {
            //if a description extension method was found returt the result
            return (string)extensionMethod.Invoke(o, new object[] { });
        }
        else
        {
            //otherwise just use ToString();
            return o.ToString();
        }
    }

上面的代码不起作用,因为扩展方法aren't found by GetMethod()。

【问题讨论】:

  • 您不能使用扩展方法隐藏实例方法,因为当编译器尝试绑定方法调用表达式时,首先会检查实例方法;有关详细信息,请参阅规范 (§7.6.5.1)。
  • 实例方法将始终在任何具有相同名称的扩展方法之前使用(其他都相同)。因此,无法以这种方式使用 ToString。

标签: c# .net extension-methods tostring typeconverter


【解决方案1】:

您是否尝试在扩展类中使用 ToString() 以外的其他名称?我也不完全确定扩展方法,但我猜 base.ToString 被调用而不是你的。可能制作一个 ToDescription() 扩展方法会产生更好的结果。

【讨论】:

  • Yes CustomObjectDescription() 正如我在 OC 中提到的,是的,它有效。问题在于没有办法知道每个 API 类型都有一个 CustomObjectDescription() 扩展方法。
【解决方案2】:

您可以为每个类似的类提供一个包装器:

    public class SomeAPITypeWrapper : SomeAPIType
    {
        public override string ToString()
        {
            return SomeProperty;
        }
    }

    public class SomeOtherAPITypeWrapper : SomeOtherAPIType
    {
        public override string ToString()
        {
            return SomeOtherProperty;
        }
    }

这当然允许根据您的问题要求使用基类/子类。它还保持它干净,并且在您的对象模型本身中,而不是在 switch 语句或帮助器类中。

【讨论】:

  • 首先,如果 API 类是密封的,我不会感到惊讶。如果没有,那么我如何获得 SomeAPItypeWrapper 的实例? API 将返回一个 SomeAPIType 实例。所以我仍然需要一些一致的方式来映射真正的 API 类型和要使用的自定义包装类 Type。
  • 是的,但是你可以使用组合而不是继承。
  • 这是“引入本地扩展”——注意事项包括:无论何时创建基类型的实例,您都需要控制创建(创建派生类型)。第二:基本类型需要是非密封的。
【解决方案3】:

如果给定的方法调用可以由实例方法和扩展方法解决,则优先使用实例方法。因此,扩展方法需要命名,使其与扩展类型中的方法名称不同。

从上面的代码看,你似乎没有控制 APIObject 的来源及其派生。所以你的选择是'Introduce Foreign Method'和'Introduce Local Extension'

  • 我会尝试使用外部方法(类似于 C# 扩展方法).. 不知道为什么需要反射。如果扩展方法不存在,那将是编译时错误。您是如何使用这种方法的?
  • 最后,switch 语句并没有那么糟糕...除非它们很长/需要频繁更改/跨位置重复。

【讨论】:

  • 还有第三种选择:使用 Cecil 将代码引入他们的库中。
  • @Joshua - 哇。不知道这是如何工作的......但这不等于组装篡改。它是否适用于强类型程序集以及 Mono/Linux 以外的其他平台?
  • 我的工具箱中有一个强大的工具,即使在 Microsoft 的 .NET 中也可以覆盖强命名,只要从你到被覆盖的树中没有混合模式程序集。 Mono.Cecil 和 Mono.Security 可以针对 Microsoft 的 .NET Framework 进行编译和使用。
【解决方案4】:

我建议创建一个Dictionary<Type,Converter<object,string>>。然后你可以寻找一个自定义的stringizer,如果没有找到,调用ToString。

注意,字典会检查类型是否完全匹配,所以如果你想处理子类,你必须编写一些额外的代码来查看字典中是否列出了基本类型(提示:如果你匹配一个基本类型类,或者即使你不这样做,继续将实际派生类型添加到字典中,这样你就不必再次递归继承树)。

请注意,您可以为Object.ToString() 构建一个符合Converter<object,string> 合约的“开放委托”,并将其用作默认值,甚至将其存储在字典中,而不是对ToString 的调用进行特殊封装.

【讨论】:

    【解决方案5】:

    您可以将所有 tostringing 推迟到您的应用程序的单独关注点。 StatePrinter (https://github.com/kbilsted/StatePrinter) 就是这样一种 API,您可以在其中使用默认值或根据要打印的类型进行配置。

    var car = new Car(new SteeringWheel(new FoamGrip("Plastic")));
    car.Brand = "Toyota";
    

    然后打印出来

    StatePrinter printer = new StatePrinter();
    Console.WriteLine(printer.PrintObject(car));
    

    你会得到以下输出

    new Car() {
        StereoAmplifiers = null
        steeringWheel = new SteeringWheel()
        {
            Size = 3
            Grip = new FoamGrip()
            {
                Material = ""Plastic""
            }
            Weight = 525
        }
        Brand = ""Toyota"" }
    

    使用 IValueConverter 抽象,您可以定义类型如何打印,使用 FieldHarvester,您可以定义要包含在字符串中的字段。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多