简体   繁体   中英

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. 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. The above setup doesn't work in that situation, because C# does not allow defining types inside an interface. 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. 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. 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.

[Edited to add: The small object does need to contain more than two fields, so KeyValuePair<K,V> won't quite cut it.]

[Edited to add further: IWidget is implemented by only one class, 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> .

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.

Otherwise, I would just define the struct with the interface, and include it as part of your 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.

Why wait? It's not like a tuple is a complicated thing. Here's the code for a 3-tuple.

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

When we eventually upgrade to .NET 4, we'll just delete the classes, or #ifdef them out

在此处忽略结构名称的含义,您可以使用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:

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> ). 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:

    Then I consider best practice to LittleThing be at the same namespace level than IWidget, preferably on a Widgets namespace. However you really should consider making LittleThing a class. 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.

  2. If all types implementing IWidget need to return slightly different values, but with similar behavior:

    Consider making an IThing interface. 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:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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