Specify interface member not by name but type

2019-07-01 18:55发布

问题:

I have a lot of similar classes generated by svcutil from some external WSDL file. Any class has a Header property and string property which named class name + "1".

For instance, I have classes: SimpleRequest that has Header property and SimpleRequest1 property.
Another one is ComplexRequest that has Header property and ComplexRequest1 property.

So, I want to create a common interface for such classes. So, basically I can define something like that:

interface ISomeRequestClass {
  string Header;
  // here is some definition for `class name + "1"` properties...
}

Is it possible to define such member in interface?


Here is post edit goes...

Here is sample of generated class:

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.MessageContractAttribute(IsWrapped=false)]
public partial class SimpleRequest
{


    public string Header;

    [System.ServiceModel.MessageBodyMemberAttribute(Name="SimpleRequest", Namespace="data", Order=0)]
    public SimpleRequestMsg SimpleRequest1;

    public SimpleRequest()
    {
    }

    public SimpleRequest(string Header, SimpleRequestMsg SimpleRequest1)
    {
        this.Header = Header;
        this.SimpleRequest1 = SimpleRequest1;
    }
}

POST EDIT 2

I changed definition of this annoying +1 property to represent real actual picture. It's all has different class types. So how can I pull it out to common interface?


POST EDIT 3

Here is coupled question that could bring more clarify.

回答1:

EDIT (after seeing your code sample): Technically speaking, your code does not have a Header property, it has a Header field. This is an important difference, since you cannot specify fields in an interface. However, using the method described below, you can add properties to your classes that return the field values.


Is it possible to define such member in interface?

No, interface names cannot be dynamic. Anyway, such an interface would not be very useful. If you had an instance of class ISomeRequestClass, what name would you use to access that property?

You can, however, use explicit interface implementation:

interface ISomeRequestClass {
    string Header { get; set; }
    string ClassName1 { get; set; }
}

class SomeClass : ISomeRequestClass {
     string Header { ... }

     string SomeClass1 { ... }

     // new: explicit interface implementation
     string ISomeRequestClass.ClassName1 {
          get { return SomeClass1; }
          set { SomeClass1 = value; }
     }
}


回答2:

You could define your interface more generally:

interface ISomeRequestClass {
  string HeaderProp {get; set;}
  string Prop {get; set;}
}

And your concrete classes could be extended (in an extra code file) by mapping interface members to class fields like so:

public partial class SimpleRequest : ISomeRequestClass
{
  public string HeaderProp
  {
    get
    {
      return Header;
    }
    set
    {
      Header = value;
    }
  }

  public string Prop
  {
    get
    {
      return SimpleRequest1;
    }
    set
    {
      SimpleRequest1= value;
    }
  }
}


回答3:

Putting aside for a moment the naming of your classes and properties.

If you're looking to create an interface with a property relevant to your specific +1 type, you have a couple of options.

Use a base class for your +1's

If both of your +1 classes inherit from the same base class you can use this in your interface definition:

public interface IFoo
{
 [...]
 PlusOneBaseType MyPlusOneObject{get;set;}
}

Create a generic property on your interface

This method allows you to specify the type for the +1 property as a generic parameter:

public interface IFoo<TPlusOneType>
{
 [...]
 TPlusOneType MyPlusOneObject{get;set;}
}

Which you might use like:

public class SimpleRequest : IFoo<SimpleRequest1>
{
 [...]
}

Update

Given that your classes are partial classes, you could always create a second (non machine generated) version of the partial class that impliments your interface.



回答4:

You mentioned svcutil so I assume you are using these classes as WCF DataContracts?

If that is the case then you could make use the name property of DataMemberAttribute.

interface IRequest 
{
    string Header { get; set; }
    string Request1 { get; set; }
}

[DataContract]
class SimpleRequest : IRequest
{
    [DataMember]
    public string Header { get; set; }

    [DataMember(Name="SimpleRequest1"]
    public string Request1 { get; set; }
}

[DataContract]
class ComplexRequest : IRequest
{
    [DataMember]
    public string Header { get; set; }

    [DataMember(Name="ComplexRequest1"]
    public string Request1 { get; set; }
}

If you are concerned giving yourself more work when you regenerate the code at some point in the future, then I recommend you write a PowerShell script to do this transformation automatically. After all svcutil is just a script written by some guy at Microsoft. It is not magic or "correct" or "standard". Your script can make a call to scvutil and then make a few quick changes to the resulting file.

 

EDIT (After seeing your edit)

You are already using MessageBodyMemberAttribute's Name property so just change this:

public string SimpleRequest1; 

To

public string Request1; 


回答5:

Do you actually need these classes to have a common interface? I'd be tempted to instead create a wrapper interface (or just a concrete class) which could then use reflection to access the fields in question:

// TODO: Make this class implement an appropriate new interface if you want
// to, for mocking purposes.
public sealed class RequestWrapper<TRequest, TMessage>
{
    private static readonly FieldInfo headerField;
    private static readonly FieldInfo messageField;

    static RequestWrapper()
    {
        // TODO: Validation
        headerField = typeof(TRequest).GetField("Header");
        messageField = typeof(TRequest).GetField(typeof(TRequest).Name + "1");
    }

    private readonly TRequest;

    public RequestWrapper(TRequest request)
    {
        this.request = request;
    }

    public string Header
    {
        get { return (string) headerField.GetValue(request); }
        set { headerField.SetValue(request, value); }
    }

    public TMessage Message
    {
        get { return (TMessage) messageField.GetValue(request); }
        get { messageField.SetValue(request, value); }
    }
}

You could use expression trees to build delegates for this if the reflection proves too slow, but I'd stick to a simple solution to start with.

The advantage of this is that you only need to write this code once - but it does mean creating a wrapper around the real request objects, which the partial class answers don't.