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 opt开发者_如何学Cion 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...
- Why use structs? Why not use classes instead?
- Declare the class separately, as a public class.
You can declare the struct
outside the interface
, but inside a nested 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
Ignoring the meaning of the struct name here, you could use generic version of 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 (e.g. EqualityComparer<T>.Default
) and even default implementations (e.g. IList<T>
and List<T>
). The above is just another case.
So, reading all you have commented here is what I think:
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.
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 { … } }
精彩评论