简体   繁体   English

声明从接口方法返回的结构

[英]Declaring a struct to be returned from an interface method

I'm writing a method in C# (2.0) that has to return a collection of simple objects. 我在C#(2.0)中编写一个必须返回简单对象集合的方法。 Normally I would do something like this: 通常我会做这样的事情:

class MyWidget
{
    struct LittleThing
    {
        int foo;
        DateTime bar;
    }

    public IList<LittleThing> LookupThings()
    {
        // etc.
    }
}

However, I have to declare this method in an interface. 但是,我必须在接口中声明此方法。 The caller doesn't get to see MyWidget , only an IWidget interface. 调用者无法看到MyWidget ,只能看到IWidget接口。 The above setup doesn't work in that situation, because C# does not allow defining types inside an interface. 上述设置在这种情况下不起作用,因为C#不允许在接口内定义类型。 What is the proper or best way to do such a declaration? 做这种声明的正确或最佳方式是什么?

The straighforward thing I thought of is to simply declare LittleThing outside of the interface. 我想到的直接的事情是简单地在界面之外声明LittleThing That doesn't seem great, for a couple of reasons. 出于几个原因,这看起来并不好。 One: it is only ever used by that single method in that single class, so it doesn't seem that LittleThing should be an independent type just floating around by itself. 一:它只用于那个单一类中的单一方法,所以LittleThing似乎不应该只是一个独立的类型。 Two: if similar methods wind up being written for other classes, they will be returning different kinds of data (for good design reasons), and I don't want to clutter the namepace with a ton of similar-named structs that differ only slightly from each other. 二:如果为其他类编写类似的方法,它们将返回不同类型的数据(出于良好的设计原因),并且我不希望使用大量相似命名的结构来混淆名称空间彼此。

If we could upgrade our version of .Net, I would just return a Tuple<> , but that's not going to be an option for some time yet. 如果我们可以升级我们的.Net版本,我只会返回一个Tuple<> ,但这还不会是一段时间的选择。

[Edited to add: The small object does need to contain more than two fields, so KeyValuePair<K,V> won't quite cut it.] [编辑添加:小对象确实需要包含两个以上的字段,因此KeyValuePair<K,V>不会完全削减它。]

[Edited to add further: IWidget is implemented by only one class, Widget . [编辑进一步补充: IWidget只由一个类Widget I think it weird to have an interface for only one class, but this was done to satisfy an old coding policy that required the contract to always be in a separate assembly from the implementation. 我认为只有一个类的接口很奇怪,但这样做是为了满足旧的编码策略,该策略要求契约始终与实现分开。 Said policy has now gone away, but we haven't the resources to refactor the entire application and remove all the unnecessary interfaces.] 所述政策现已消失,但我们没有资源重构整个应用程序并删除所有不必要的接口。]

What's the best practice? 什么是最佳做法?

If the "LittleThing" only has two values, you can return a KeyValuePair<TKey,TValue> . 如果“LittleThing”只有两个值,则可以返回KeyValuePair<TKey,TValue>

If there are more than two, you could always make your own Tuple class, and replace it with .NET 4's when you finally do move to .NET 4. 如果有两个以上,你可以随时创建自己的Tuple类,并在最终转移到.NET 4时将其替换为.NET 4。

Otherwise, I would just define the struct with the interface, and include it as part of your API. 否则,我只想用接口定义结构,并将其作为API的一部分包含在内。 Namespaces take care of the naming concern... 命名空间负责命名问题......

  1. Why use structs? 为什么要使用结构? Why not use classes instead? 为什么不使用类呢?
  2. Declare the class separately, as a public class. 单独声明该类,作为公共类。

您可以在interface外部声明struct ,但在嵌套的namespace

If we could upgrade our version of .Net, I would just return a Tuple<>, but that's not going to be an option for some time yet. 如果我们可以升级我们的.Net版本,我只会返回一个元组<>,但这还不会是一段时间的选择。

Why wait? 干嘛要等? It's not like a tuple is a complicated thing. 它不像元组是一件复杂的事情。 Here's the code for a 3-tuple. 这是3元组的代码。

public struct Tuple<TItem1, TItem2, TItem3>
{
    public Tuple(TItem1 item1, TItem2 item2, TItem3 item3)
    {
        this = new Tuple<TItem1, TItem2, TItem3>();
        Item1 = item1;
        Item2 = item2;
        Item3 = item3;
    }

    public static bool operator !=(Tuple<TItem1, TItem2, TItem3> left, Tuple<TItem1, TItem2, TItem3> right)
    { return left.Equals(right); }

    public static bool operator ==(Tuple<TItem1, TItem2, TItem3> left, Tuple<TItem1, TItem2, TItem3> right)
    { return !left.Equals(right); }

    public TItem1 Item1 { get; private set; }
    public TItem2 Item2 { get; private set; }
    public TItem3 Item3 { get; private set; }

    public override bool Equals(object obj)
    {
        if (obj is Tuple<TItem1, TItem2, TItem3>)
        {
            var other = (Tuple<TItem1, TItem2, TItem3>)obj;
            return Object.Equals(Item1, other.Item1)
                && Object.Equals(Item2, other.Item2)
                && Object.Equals(Item3, other.Item3);
        }
        return false;
    }

    public override int GetHashCode()
    {
        return ((this.Item1 != null) ? this.Item1.GetHashCode() : 0)
             ^ ((this.Item2 != null) ? this.Item2.GetHashCode() : 0)
             ^ ((this.Item3 != null) ? this.Item3.GetHashCode() : 0);
    }
}

As you can see, it's no big deal. 如你所见,这没什么大不了的。 What I've done on my current project is implement 2, 3 and 4-tuples, along with a static Tuple class with Create methods on it, which exactly mirror the .NET 4 tuple types. 我在当前项目中所做的是实现2,3和4元组,以及带有Create方法的静态Tuple类,它完全反映了.NET 4元组类型。 If you're really paranoid you can use reflector to look at the dissassembled source code for the .NET 4 tuple, and copy it verbatim 如果你真的是偏执狂,你可以使用反射器来查看.NET 4元组的反汇编源代码,并逐字复制

When we eventually upgrade to .NET 4, we'll just delete the classes, or #ifdef them out 当我们最终升级到.NET 4时,我们只会删除这些类,或者#ifdef它们

在此处忽略结构名称的含义,您可以使用KeyValuePair的通用版本。

Interfaces define only what can be implemented on another class, which is why you cannot give anything inside of them a definition. 接口只定义了可以在另一个类上实现的内容,这就是为什么你不能在它们内部给出任何定义的原因。 Including classes and structs. 包括类和结构。

That said, one pattern often used to get around this restriction is to define a class with the same name (excluding the 'I' prefix) to provide any related definitions, such as: 也就是说,通常用于解决此限制的一种模式是定义具有相同名称的类(不包括“I”前缀)以提供任何相关定义,例如:

public interface IWidget
{
    IList<Widget.LittleThing> LookupThings();
}

// For definitions used by IWidget
public class Widget
{
    public struct LittleThing
    {
        int foo;
        DateTime bar;
    }
}

Examples of this pattern can be found in the BCL, particularly with generics and extension methods but also with default values (eg EqualityComparer<T>.Default ) and even default implementations (eg IList<T> and List<T> ). 此模式的示例可以在BCL中找到,特别是使用泛型和扩展方法,但也可以使用默认值(例如EqualityComparer<T>.Default )甚至默认实现(例如IList<T>List<T> )。 The above is just another case. 以上只是另一种情况。

So, reading all you have commented here is what I think: 所以,阅读你所评论的所有内容是我的想法:

  1. If all types implementing IWidget return LittleThing: 如果实现IWidget的所有类型都返回LittleThing:

    Then I consider best practice to LittleThing be at the same namespace level than IWidget, preferably on a Widgets namespace. 然后我认为LittleThing的最佳实践与IWidget处于相同的命名空间级别,最好是在Widgets命名空间。 However you really should consider making LittleThing a class. 但是你真的应该考虑让LittleThing成为一个类。 By your description of the problem it seems it can be not so little, as everytime class implementing IWidget misght use some fields but not others. 通过你对问题的描述,似乎它可能不会那么少,因为每次实现IWidget的类都会使用某些字段而不是其他字段。

  2. If all types implementing IWidget need to return slightly different values, but with similar behavior: 如果实现IWidget的所有类型都需要返回稍微不同的值,但具有类似的行为:

    Consider making an IThing interface. 考虑制作IThing界面。 Then every IThing implementation would be entirely dependant on the class that returns it, so then you can declare the structure or class implementing IThing inside the class implementing IWidget like this: 然后每个IThing实现都完全依赖于返回它的类,那么你就可以在实现IWidget的类中声明实现IThing的结构或类,如下所示:

     interface IWidget { IList<IThing> LookupThings() { … } } interface IThing { … } class MyWidget : IWidget { IList<IThing> IWidget.LookupThings() { … } private class MyWidgetThings : IThing { … } } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM