Rob Stevenson-Leggett
Rob Stevenson-Leggett

Reputation: 35689

C# Generics: Can I constrain to a set of classes that don't implement an interface?

I have 3 classes that are essentially the same but don't implement an interface because they all come from different web services.

e.g.

They all have the same properties and I am writing some code to map them to each other using an intermediary object which implements my own interface IObject1

I've done this using generics

public static T[] CreateObject1<T>(IObject1[] properties)
  where T : class, new()
{
   //Check the type is allowed
   CheckObject1Types("CreateObject1<T>(IObject1[])", typeof(T));
   return CreateObjectArray<T>(properties);
}

private static void CheckObject1Types(string method, Type type)
{
  if (type == typeof(Service1.Object1)
  || type == typeof(Service2.Object1)
  || type == typeof(Service3.Object1)
  || type == typeof(Service1.Object1[])
  || type == typeof(Service2.Object1[])
  || type == typeof(Service3.Object1[]))
  {
     return;
  }

  throw new ArgumentException("Incorrect type passed to ServiceObjectFactory::" + method + ". Type:" + type.ToString());
}

My client code looks like:

//properties is an array of my intermediary objects
Object1[] props = ServiceObjectFactory.CreateObject1<Object1>(properties);

What I want to do is get rid of the CheckObject1Types method and use constraints instead so that I get a build error if the types aren't valid, because at the moment I can call this method with any type and the ArgumentException is thrown by the CheckObject1Types method.

So I'd like to do something like:

public static T[] CreateObject1<T>(IObject1[] properties)
  where T : class, new(), Service1.Object1|Service2.Object1|Service3.Object1
{
   return CreateObjectArray<T>(properties);
}

Any ideas?

Edit: I don't want to change the Reference.cs files for each webservice because all it takes is a team mate to update the web reference and BAM! broken code.

Upvotes: 5

Views: 656

Answers (4)

Ricardo Villamil
Ricardo Villamil

Reputation: 5107

I would write a converter class that took either of your three objects into a new object that supports the interface you want. Furthermore, I would use reflection so you don't have to type all the assignments manually (unless it's a small object and is not expected to change too much).

Using Reflection would also give you the guarantee that you want to make sure the objects implement whatever properties your new Interfaced object implements, otherwise when a property you expect is not implemented you could have it throw an error.

Upvotes: 0

Scott Dorman
Scott Dorman

Reputation: 42526

Constraining to a list of classes in an "OR" fashion like you want to do isn't possible in C#. (In fact, I'm not even sure it's legal directly in IL either.)

Your only option is to keep using the checktypes style functions. If you own the code for the different webservices, you can also implement a "sentinel" interface and use that as your constraint. I know sentinel interfaces are not recommended practice per the Framework Design Guidelines, but they occasionaly have their uses (this being one of them).

As Jon points out, you may be able to make use of prtial classes in order to implement a common interface. If your References.cs implements a class:

namespace TestServices
{
   internal partial class Service1SoapClient : System.ServiceModel.ClientBase<T>, K
   {
   }
}

You would then create another partial class in the same namespace (call it References.CommonInterface.cs), which can live anywhere in your project, with the following:

namespace TestServices
{
   internal interface ICommon
   {
   }

   internal partial class Service1SoapClient : ICommonInterface
   {
   }
}

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1503110

Assuming the generated classes are partial, you can create an interface and then add another partial source file to make your generated classes implement the interface. Then you can constrain by interface as normal. No changes to the actual generated code required :)

Upvotes: 14

Joel Coehoorn
Joel Coehoorn

Reputation: 416121

If you're pulling these objects from a web service you ulitmately do have control over the class definitions used. They don't just spring up out of thin air (even if you do have code generator or visual studio creating them initially). There's still a class file for each of them somewhere that must be compiled with the app, and you should be able to add your common interface to those class definitions.

Upvotes: -2

Related Questions