views:

5102

answers:

13

OK, here we go again! Following on from my previous question on Converting to a Generic Type from String, I thought I would ask another Generics-related question!

This time, is there a way to enforce/limit the types that are passed to PRIMITIVE's? (bool, int, string, etc)

Now, I know you can limit the generic type parameter to a type or interface implementation via the where clause, however, this doesn't fit the bill for primitives (AFAIK) because the do not all have common ground (apart from object before someone says! :P).

So, my current thoughts are to just grit my teeth and do a big switch statement and throw an ArgumentException on failure..

Just thought I would check here to see if the awesome pool of wisdom that is StackOverflow could offer any better ideas? :)

Many thanks.


EDIT Just to clarify:

The code definition should be like:

public class MyClass<GenericType> ....

And instantiation:

MyClass<bool> = new MyClass<bool>(); // Legal
MyClass<string> = new MyClass<string>(); // Legal
MyClass<DataSet> = new MyClass<DataSet>(); // Illegal
MyClass<RobsFunkyHat> = new MyClass<RobsFunkyHat>(); // Illegal (but looks awesome!)


EDIT: This was so simple following on from Jon Limjaps Pointer.. So simple I almost want to cry, but it's great because the code works like a charm!

So here is what I did (you'll laugh!):

Code Added to the Generic Class

bool TypeValid()
{
    // Get the TypeCode from the Primitive Type
    TypeCode code = Type.GetTypeCode(typeof(PrimitiveDataType));

    // All of the TypeCode Enumeration refer Primitive Types
    // with the exception of Object and Empty (Null).
    // Since I am willing to allow Null Types (at this time)
    // all we need to check for is Object!
    switch (code)
    {
        case TypeCode.Object:
            return false;
        default:
            return true;
    }
}

Then a little utility method to check the type and throw an Exception...

private void EnforcePrimitiveType()
{
    if (!TypeValid())
        throw new InvalidOperationException(
            "Unable to Instantiate SimpleMetadata based on the Generic Type of '" + typeof(PrimitiveDataType).Name + 
            "' - this Class is Designed to Work with Primitive Data Types Only.");
}

All that then needs to be done is to call EnforcePrimitiveType() in the classes constructors.. Job done! :)

The only one downside, it only throws an Exception at runtime (obviously) rather than design time.. But thats no big deal and could be picked up with utilities like FxCop (which we don't use at work).

Special thanks to Jon Limjap on this one!

+26  A: 
public class Class1<GenericType> where GenericType : struct
{
}

This one seemed to do the job..

Lars Mæhlum
A: 

@Lars, thanks for the answer, but you missed the key thing.. AFAIK Primitives don't have a common BaseType (apart from Object which is useless).. I want to limit the generic to work with any primitive, but nothing else.

Rob Cooper
A: 

Well, I don't know how but a main point of contention between objects and primitves is that most primitives are actually structs, not classes.

I'll look for a way to figure them out programmatically :)

Jon Limjap
+4  A: 

@Jon Limjap - Good point, and something I was already considering.. I'm sure there is a generic method that can be used to determine if the type is of a value or reference type..

This could be useful in instantly removing a lot of the objects I dont want to deal with (but then you need to worry about the structs that are used such as Size ).. Interesting problem no? :)

EDIT: Ah yes, here it is:

where T : struct

Taken from MSDN.

Rob Cooper
A: 

How about a custom FxCop rule that flags undesirable usage of MyClass<>?

JoshL
+12  A: 

Primitives appear to be specified in the TypeCode enumeration:

http://msdn.microsoft.com/en-us/library/system.typecode.aspx

Perhaps there is a way to find out if an object contains the TypeCode enum without having to cast it to an specific object or call GetType() or typeof()?

Update It was right under my nose. The code sample there shows this:

static void WriteObjectInfo(object testObject)
{
    TypeCode    typeCode = Type.GetTypeCode( testObject.GetType() );

    switch( typeCode )
    {
        case TypeCode.Boolean:
            Console.WriteLine("Boolean: {0}", testObject);
            break;

        case TypeCode.Double:
            Console.WriteLine("Double: {0}", testObject);
            break;

        default:
            Console.WriteLine("{0}: {1}", typeCode.ToString(), testObject);
            break;
        }
    }
}

It's still an ugly switch. But it's a good place to start!

Jon Limjap
A: 

I'm curious.. I wonder if this could be done in .NET 3.x using extension methods.. Create an interface.. Implement the interface in the extension methods.. (which would probably be cleaner than a bit fat switch).. Plus if you then need to later extend to any lightweight custom types, they can also implement the same interface, with no changes required to the base code.

What do you guys think?

Sad news is I am working in framework 2!! :D

EDIT: Leaving the office in like 5 mins, will pick this up when I get back home! :D

Rob Cooper
+1  A: 

Pretty much what @Lars already said:

//force T to be a value (primitive) type
public class Class1<T> where T: struct

//force T to be a reference type
public class Class1<T> where T: class

//force T to be a parameterless constructor
public class Class1<T> where T: new()

All work in .Net 2, 3 and 3.5

Keith
The last one must be `public class Class1<T> where T: new()`
Braveyard
Cheers - amended
Keith
A: 

public class Class1 where GenericType : struct { }

This one seemed to do the job..

The problem I see with that implementation is that if you have a custom struct it will still come across as a primitive, which is not the case.

Jon Limjap
A: 

@Jon Limjap Brilliantly done my friend, I knew I should have investigated more in the Type class. I will knock some code together and see how it runs! +1 for this, and may well end up the answer! :)

Thanks again!

@Keith Thanks for the input, however, you didn't read the earlier comments! We are aware of the where clause being able to partially constrain the generic type, but it doesn't enforce it! Thanks for the input though!

Rob Cooper
A: 

If you can tolerate using factory methods (instead of the constructors MyClass you asked for) you could always do something like this:

class MyClass<T>
{
  private readonly T _value;

  private MyClass(T value) { _value = value; }

  public static MyClass<int> FromInt32(int value) { return new MyClass<int>(value); }
  public static MyClass<string> FromString(string value) { return new MyClass<string>(value); }
  // etc for all the primitive types, or whatever other fixed set of types you are concerned about
}

A problem here is that you would need to type MyClass<AnyTypeItDoesntMatter>.FromInt32, which is annoying. There isn't a very good way around this if you want to maintain the private-ness of the constructor, but here are a couple of workarounds:

  • Create an abstract class MyClass. Make MyClass<T> inherit from MyClass and nest it within MyClass. Move the static methods to MyClass. This will all the visibility work out, at the cost of having to access MyClass<T> as MyClass.MyClass<T>.
  • Use MyClass<T> as given. Make a static class MyClass which calls the static methods in MyClass<T> using MyClass<AnyTypeItDoesntMatter> (probably using the appropriate type each time, just for giggles).
  • (Easier, but certainly weird) Make an abstract type MyClass which inherits from MyClass<AnyTypeItDoesntMatter>. (For concreteness, let's say MyClass<int>.) Because you can call static methods defined in a base class through the name of a derived class, you can now use MyClass.FromString.

This gives you static checking at the expense of more writing.

If you are happy with dynamic checking, I would use some variation on the TypeCode solution above.

Doug McClean
A: 

You can simplify the EnforcePrimitiveType method by using typeof(PrimitiveDataType).IsPrimitive property. Am I missing something?

Vivek
Passing a string into that statement returns False, and the poster specified he needed string to return True. .NET considers char to be a primitive, but not string.
Nicholas
A: 

@Rob, Enum's will slip through the TypeValid function as it's TypeCode is Integer. I've updated the function to also check for Enum.

Private Function TypeValid() As Boolean
 Dim g As Type = GetType(T)
 Dim code As TypeCode = Type.GetTypeCode(g)

 ' All of the TypeCode Enumeration refer Primitive Types
 ' with the exception of Object and Empty (Nothing).
 ' Note: must also catch Enum as it's type is Integer.
 Select Case code
  Case TypeCode.Object
   Return False
  Case Else
   ' Enum's TypeCode is Integer, so check BaseType
   If g.BaseType Is GetType(System.Enum) Then
    Return False
   Else
    Return True
   End If
 End Select
End Function
Matt Boehlig