1

I have the following example code. Waht is the best practice? Compare Values or compare Types to perform a certain business logic:

public Customer
{
    public Category {get;set;}
    public CategoryName {get;set;}  //e.g. "Category A" or "Category B"
}

public Category{}

public CategoryA : Category {}

public CategoryB : Category {}

public void Main()
{
    Customer customer = new Customer();

// Option 1:
if(customer.CategoryName == "Category A")
{
    CategoryA catA= customer.Category as CategoryA;     
    DoSomething(catA)
}

// Option 2:
CategoryA catA= customer.Category as CategoryA;
if(catA != null)
{
    DoSomething(catA)
}

// Option 3:
if(customer.Catgeory is Category A)
{
    CatgeoryA catA= customer.Category as CategoryA;
    DoSomething(catA)
}
}

The Code is just for illustration.

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
LuckyStrike
  • 1,483
  • 3
  • 12
  • 23
  • 2
    If you are going to do something different based on each type, then I would consider putting that functionality in the derrived class implementations themselves. – Justin Harvey Nov 30 '12 at 14:09

7 Answers7

4

Given those 3 options, I'd go with Option 2.
e.g. - Try to make a conversion and check if it isn't Null.

The reason it is better then the last option is that Option 3 causes you to make 2 conversions, one for the is and one for the as in the next line.

Finally, Option 1 is the worst IMO - it requires you to have some kind of logic that you can't really be sure will stick later on (No one is preventing someone from creating a customer with a Category of CategoryA and a CategoryName of "Category B"). Also, this is additional logic which can be done in a much clearer way via Option 2.

I should mention, as pointed out in the other comments/answers, there are a few more options that can be taken into account. Using Polymorphism is probably the best design strategy.

Blachshma
  • 17,097
  • 4
  • 58
  • 72
  • 2 and 3 are effectively the same from a design point of view; its two slight variations on the same general approach of checking the type. As for the importance of the difference; it's one type check. If that's not a microoptimization I don't know what is. It is trivially easy for a computer to do such a type check, especially considering we know the object's state is already in the cache. As for proper design, proper design is "none of the above". He should be using polymorphism and defining the differences in behavior within each derived class and calling it through a method in the base. – Servy Nov 30 '12 at 15:00
  • When you say, "And I quote" you should follow it with an exact quote, rather than something the person didn't actually say. His actual words are: "I have the following example code. Waht is the best practice?" He asked what is the best practice, and the best practice is to not use any of the three options list; it's to use polymorphism. – Servy Nov 30 '12 at 15:48
  • And yet his question didn't constrain you to those choices. It's clearly implied, I'll grant you that, but it's clear that those were simply the OP's attempts at solving the problem, and it's most helpful to them to provide the solution that best solves that problem, regardless of whether or not it's a solution he's tried. If you wanted to include which of the three is best as well as what you consider the best in general, that'd be fine, but as it is you've already said you know what's best, and it's not what's in your answer. That's not helpful to the OP in the end. – Servy Nov 30 '12 at 15:52
3

I think there is something wrong with your design. It is a smelly design IMO to have multiple checks and casts.

CategoryA catA = customer.Category as CategoryA;
if(catA != null)
{
    DoSomething(catA)
}

So presumably you have a DoSomething(CategoryA cat), DoSomething(CategoryB cat) etc.

In this case I would strongly recommend you consider moving the DoSomething to the category class.

customer.Category.DoSomething();

It can then be implemented in different ways by CategoryA, CategoryB and CategoryC.

If there only is an implementation for CategoryA, just have an empty implementation in the base category and don't override in B and C's.

Extra recommendation: Use Interfaces

I would personally not implement basetypes. I would always opt for interfaces. It's less restrictive and easier to follow when the code is not in several layers of class hierarchy.

public interface ICategory{
  void DoSomething();
}

public class CategoryA : ICategory {...}

public class CategoryB : ICategory {...}

Common functionality between the interface implementers? No problem, just make a new object to perform that functionaility and compose it into both of the implementers. Then that functionality can be unit tested alone.

weston
  • 54,145
  • 21
  • 145
  • 203
1

In addition to the other points explained by other answerers about performance or cast once or twice, or whatever, I'd like to make some considerations.

It absolutely depends on what the business would do in your particular case:

a. Category is just a discriminator

Don't use a type. It should be a string, or an identifier like a Guid or int. Your UI localization would translate the identifier into the human-readable name. This is enough to discriminate by category.

Solution: Customer would have a CategoryName property. Compare by Id.

b. Category is a discriminator and there's a limited number of possible categories

One word: enumerations. Define a Category enumeration:

public enum Category { A, B, C }

if(customer.Category == Category.A)
{
    // Whatever
}

Solution: Customer would have a Category property. Compare by enumeration value.

c. Category is an entity

I wouldn't create a class for each category. Category is the entity and there're zero or more categories having particular identifiers or names.

For that reason, Category has a Name and Id property of string and int or Guid type, respectively.

Solution: Customer would have an 1:1 association with Category and Category would have a Name property. Compare by Id.

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
0

This is a bit subjective depending on your situation. In my opinion, it is always better to compare by type instead of by string for the single fact that you can mis-type a string but the compiler will check if you mistype a type. Between options 2 and 3, there really doesn't matter other than skipping a second cast with option 2.

Jordan Parmer
  • 36,042
  • 30
  • 97
  • 119
0

Option 2 is always better than Option 3 as it saves a 2nd cast. Therefore, we can eliminate Option 3.

As for Option 1, I have to ask why are you duplicating the type name in a property? This is redundant code (as you already have the type name).

Therefore, Option 2 is the best, given the scenario you have described. However, a little more detail about what you are checking, how likely it is to change, how closely the category name is tied to the class name, etc. would probably influence the decision...

RB.
  • 36,301
  • 12
  • 91
  • 131
  • Can you elaborate more about why 2nd option save a cast? Perhaps I'm wrong but `is` is a type operator so where's the cast? – Matías Fidemraizer Nov 30 '12 at 14:03
  • Nevermind. I got this other SO question: http://stackoverflow.com/questions/686412/c-sharp-is-operator-performance – Matías Fidemraizer Nov 30 '12 at 14:04
  • If using `as` vs `is` is making a significant performance difference to your application you have *major* problems already. Doing a type check is not a slow operation; it's in fact very fast. – Servy Nov 30 '12 at 15:02
  • @Servy I know that. The point is that option 2 and 3 are equivalent - the only distinction is that option 3 is infintessimaly slower, so we can eliminate it. This cuts his options by 33%, simplifying his question - really, he has asked a question about 2 approaches, not 3. – RB. Dec 03 '12 at 09:35
0

Edit 3:

I think that in this question author did not mean that objects are POCO's, because if Category is a POCO then it can not be polymorphic. And because of that there is no sense to inherit CategoryA and CategoryB from Category - why we would want to have the same interface without polymorphism?! So conclusion is that author of this question made a design mistake or he should think about my Original answer.

Edit 2:

I just noticed that in Customer we do not know concrete type of Category. So we can not use CategoryService as:

new CategoryService().DoSomething(customer.Category); // compile-time error

So Edit 1 was not valid for this case.

Edit 1:

For POCO you can use service class with method overloading:

public class Customer {
    public Category Category { get; set; }
}

public abstract class Category {
}

public class CategoryA : Category {
}

public class CategoryB : Category {
}

public class CategoryService {
    public void DoSomething(CategoryA c) {
        Console.WriteLine("A");
    }

    public void DoSomething(CategoryB c) {
        Console.WriteLine("B");
    }
}

Original:

Best practice is to use polymorphism. When you need to compare types it is a sign that you did something wrong or it is very special situation.

public class Customer {
    public Category Category { get; set; }
}

public abstract class Category {
    public abstract void DoSomething();
}

public class CategoryA : Category {
    public override void DoSomething()
    {
        Console.WriteLine("A");
    }
}

public class CategoryB : Category {
    public override void DoSomething()
    {
        Console.WriteLine("B");
    }
}
mixel
  • 25,177
  • 13
  • 126
  • 165
  • It's the best practice if you're using something like *active record*. What about POCO? – Matías Fidemraizer Nov 30 '12 at 14:13
  • Method overloading like in Visitor pattern. – mixel Nov 30 '12 at 14:20
  • what does visitor pattern to do with POCO? I mean that if you want to leave your entities "as is" without garbage (properties only!), this wouldn't be a "good practice". – Matías Fidemraizer Nov 30 '12 at 14:36
  • @MatíasFidemraizer Actually the opposite is true. It's not "garbage". The entire design of classes in OOP is to combine methods with the data they act on into a single entity. In more procedural programming they were divided; you had data holders and separately you had methods. OOP came about because of the problems with that type of design. The Visitor pattern is a mess; it's a very high maintenance pattern to use, and should only be used when you have few options. Using polymorphism here, rather than reversing it, is *much* better design. – Servy Nov 30 '12 at 15:05
  • @Servy First of all, I'm agree with you about what OOP is and what's its goal. You're right. But there're more than a way of creating good OOP solutions. I don't like the idea of having *domain objects* with behaviors (i.e. *methods*). An entity is that: an entity. An entity itself does nothing, for that reason exists an *entity manager* which performs domain operations, and the repository handles domain I/O or CRUD. That's why I find any method in a domain entity or object pure garbage. – Matías Fidemraizer Nov 30 '12 at 15:11
  • @mixel Now, now!! I liked your edit :) In my case, I call them as *domain managers* rather than services, but I prefer your edit instead of your original answer! – Matías Fidemraizer Nov 30 '12 at 15:13
  • 1
    @MatíasFidemraizer Yet those domain object don't actually have methods for interacting with the database, such operations are performed *on* them, not *by* them. In either case, the edit of this post is pretty bad; if you have `DoSomething` overloads each accepting a different type it effectively forces you to `switch` on the type to figure out which to call. That's poor design. If you find it imperative to remove code from the actual data holder then you should use the adapter pattern; provide a wrapper type for each domain object which uses inheritance/polymorphism to solve this problem. – Servy Nov 30 '12 at 15:16
  • @Servy That's a matter of choice and design. You need to consider specific case of this situation. Otherwise, holywar is meaningless :-) – mixel Nov 30 '12 at 15:20
  • @Servy Remember that we don't know **anything** about OP actual implementation. I upvoted the edit because I prefer the method overloading instead of having POCO with methods. – Matías Fidemraizer Nov 30 '12 at 15:20
  • @Servy When I need methods like you mention in domain objects, I prefer to use extension methods. But I really think that POCO domain objects should be populated with properties. – Matías Fidemraizer Nov 30 '12 at 15:23
  • @MatíasFidemraizer Extension methods wouldn't help you here. They must be static and therefore cannot be a part of any inheritance structure. As I said in my previous post, there are ways of moving all or most of the code out of the data holder object (although I disagree that it's needed) while still using an inheritance structure and polymorphism. Using an overloaded method doesn't "solve" the problem in the OP, it just moves it, since you need to switch on the type to know what overload to call. – Servy Nov 30 '12 at 15:46
  • @Servy ... Extension methods are static, but you get the instance of the object anyway. It's a good way of adding methods to a class without designing them in the class itself. That's good because the POCO is still POCO. – Matías Fidemraizer Nov 30 '12 at 15:59
  • @MatíasFidemraizer And yet it's useless in this context since the objects are all cast to a parent type. – Servy Nov 30 '12 at 16:03
  • @Servy Did you note I was talking in general terms?? – Matías Fidemraizer Nov 30 '12 at 16:25
  • And did you note that my response is stating that, regardless of whether or not it's a useful technique in general terms it's not a useful technique in this situation? – Servy Nov 30 '12 at 16:32
0

Why not implement an override method in the parent class as such:

public override bool Equals(System.Object obj)
{
    bool equal=true;

    //any kind of business logic here on the object values andor types

    return equal;
}

We do it all the time, works great :)

povilasp
  • 2,386
  • 1
  • 22
  • 36
  • `Equals` can be used to compare one `Category` instance to another. But he only has one `Category` object, `customer.Category`. Also implementing Equals at a base level is a bad idea as can easily break the Equals contract, rules such as i.e. if `A=B=C` then `A=C`. `A=B` then `B=A` etc. – weston Nov 30 '12 at 15:13