views:

253

answers:

3

hey guys, I've removed some of the complexities of my needs to the core of what I need to know.

I want to send a collection of Values to a method, and inside that method I want to test the Value against, say, a property of an Entity. The property will always be of the same Type as the Value.

I also want to test if the value is null, or the default value, obviously depending on whether the value type is a reference type, or a value type.

Now, if all the values sent to the method are of the same type, then I could do this using generics, quite easily, like this:

public static void testGenerics<TValueType>(List<TValueType> Values) {

        //test null/default
        foreach (TValueType v in Values) {
            if (EqualityComparer<TValueType>.Default.Equals(v, default(TValueType))) {
                //value is null or default for its type
            } else {
                //comapre against another value of the same Type
                if (EqualityComparer<TValueType>.Default.Equals(v, SomeOtherValueOfTValueType)) {
                    //value equals
                } else {
                    //value doesn't equal
                }
            }
        }
    }

My questions is, how would I carry out the same function, if my Collection contained values of different Types.

My main concerns are successfully identifying null or default values, and successfully identifying if each value passed in, equals some other value of the same type.

Can I achieve this by simply passing the type object? I also can't really use the EqualityComparers as I can't use generics, because I'm passing in an unknown number of different Types.

is there a solution?

thanks

UPDATE

ok, searching around, could I use the following code to test for null/default successfully in my scenario (taken from this SO answer):

object defaultValue = type.IsValueType ? Activator.CreateInstance(type) : null;

I reckon this might work.

Now, how can I successfully compare two values of the same Type, without knowing their types successfully and reliably?

+1  A: 

The short answer is "yes", but the longer answer is that it's possible but will take a non-trivial amount of effort on your part and some assumptions in order to make it work. Your issue really comes when you have values that would be considered "equal" when compared in strongly-typed code, but do not have reference equality. Your biggest offenders will be value types, as a boxed int with a value of 1 won't have referential equality to another boxed int of the same value.

Given that, you have to go down the road of using things like the IComparable interface. If your types will always specifically match, then this is likely sufficient. If either of your values implements IComparable then you can cast to that interface and compare to the other instance to determine equality (==0). If neither implements it then you'll likely have to rely on referential equality. For reference types this will work unless there is custom comparison logic (an overloaded == operator on the type, for example).

Just bear in mind that the types would have to match EXACTLY. In other words, an int and an short won't necessarily compare like this, nor would an int and a double.

You could also go down the path of using reflection to dynamically invoke the Default property on the generic type determined at runtime by the supplied Type variable, but I wouldn't want to do that if I didn't have to for performance and compile-time safety (or lack thereof) reasons.

Adam Robinson
thanks Adam! Yes, the Types being compared will always be exactly the same. IComparable might be the way to go, though I'm not aware of it. Could you provide a short code snippet just to get me started. Also, would this approach take into account Nullable types?
andy
hey Adam...Now that I think about it, I'm unsure of how to implement IComparable. I'm working with LINQ2SQL entities, and they don't implement that interface. Could the values I pass in implement it, how?
andy
Yes, you'd use IComparable on the individual values, not the entities themselves.
Adam Robinson
+1  A: 

Is the list of types you need to test a pre-determined list? If so, you can use the Visitor Pattern (and maybe even if not since we have Generics). Create a method on your Entities (can be done using partial classes) that takes in an interface. Your class then calls a method on that interface passing itself. The interface method can be generic, or you can create an overload for each type you want to test.

Battery about to die otherwise would give example.


Fifteen seconds after hitting "Save" the machine went into hibernate.

After thinking about it, the Visitor pattern might not solve your specific problem. I thought you were trying to compare entities, but it appears you are testing values (so potentially ints and strings).

But for the sake of completion, and because the visitor pattern is kind of cool once you realize what it does, here's an explanation.

The Visitor pattern allows you to handle multiple types without needing to figure out how to cast to the specific type (you decouple the type from the item using that type). It works by having two interfaces - the visitor and the acceptor:

interface IAcceptor
{
  void Accept(IVisitor visitor);
}

interface IVisitor
{
  void Visit(Type1 type1);
  void Visit(Type2 type2);
  .. etc ..
}

You can optionally use a generic method there:

interface IVisitor
{
  void Visit<T>(T instance);
}

The basic implementation of the accept method is:

  void Accept(IVisitor visitor)
  {
    visitor.Visit(this);
  }

Because the type implementing Accept() knows what type it is, the correct overload (or generic type) is used. You could achieve the same thing with reflection and a lookup table (or select statement), but this is much cleaner. Also, you don't have to duplicate the lookup among different implementations -- various classes can implement IVisitor to create type-specific functionality.

The Visitor pattern is one way of doing "Double Dispatch". The answer to this question is another way and you might be able to morph it into something that works for your specific case.

Basically, a long-winded non-answer to your problem, sorry. :) The problem intrigues me, though -- like how do you know what property on the entity you should test against?

Talljoe
haha, no problem Talljoe, thanks for the heads up. Am trying reflection at the mo, but if you could provide an example later that would be awesome, as I'm not entirely sure how to do what you've described. Also, I guess the list of Types is predetermined per entity, though the list would not necessarily contain all types, and would of course be different for each Entity
andy
cool, thanks for the explanation talljoe, much appreciated. As to your question, you'd know which fileds to check because in reality, I'd actually be sending in a dictionary<string,object>, where the key is the entity propertyname, and the value is the value to check.
andy
Visitor is **definitely not** what should be used here. In fact, you're re-implementing built-in virtual method dispatch by using it here.
Alex Yakunin
Virtual-method dispatch only works when the callee changes behavior. You use the Visitor pattern when ther *caller* wants to change behavior based on the callee's type.
Talljoe
+1  A: 

There is Object.Equals(object left, object right) static method, it internally relies on Equals(object) implementation available at one of provided arguments. Why do you avoid using it?

The rules of implementing equality members are nearly the following:

  1. Required: Override Equals(object) and GetHashCode() methods
  2. Optional: Implement IEquatable<T> for your type (this is what EqualityComparer.Default relies on)
  3. Optional: Implement == and != operators

So as you see, if you'll rely on object.Equals(object left, object right), this will be the best solution relying on strongly required part of equality implementation pattern.

Moreover, it will be the fastest option, since it relies just on virtual methods. Otherwise you'll anyway involve some reflection.

public static void TestGenerics(IList values) {
  foreach (object v in values) {
    if (ReferenceEquals(null,v)) {
      // v is null reference
    }
    else {
      var type = v.GetType();
      if (type.IsValueType && Equals(v, Activator.CreateInstance(type))) {
        // v is default value of its value type
      }
      else {
        // v is non-null value of some reference type
      }
    }
  }
}
Alex Yakunin
cool, thanks Alex. In regards to your comments, I don't completely understand, but i think we're on the right track. Could you supply some code snippets, showing me how I'd implement what you're talking about? cheers
andy
Just added the code to this post.
Alex Yakunin
Notes: Activator.CreateInsttance is rather slow - I'd advise you to cache results of its invocations.
Alex Yakunin