Properly use of Generics Class

Started by
6 comments, last by Alberth 4 years, 6 months ago

hello!

i'm actually working with an already working module that i have to convert to Generic, what i mean is that i have this module that is working on DI and i need to allow it to get a generic DI during the factory creation.

i learned generics but i never really used it.. so will be enought to pass the superclass as generic parameters on <T> and reference it as T in all of the code inside the module?

what for the reference to that module? should be ok to just reference to it like

private MyModule<TSuperClass> myModule; ?

Advertisement
using System;



namespace ConsoleApp1

{



   

    class Program

    {

       

        static void Main(string[] args)

        {

            Executer executer = new Executer(new Result<DoWork>(new DoWorkNewTwo()));







            Console.ReadLine();

        }

    }



    public class Executer

    {

        private Result<DoWork> _work;



        public Executer (Result<DoWork> in_result)

        {

            _work = in_result;

        }



        public void Print()

        {

            _work.GetWork.Print();

        }

    }



    public class Result<TWork>

        where TWork : DoWork

    {



        public TWork GetWork { get; set; }



        public Result(TWork in_work)

        {

            GetWork = in_work;

        }

    }

}

this is what i mean, in this case i can store the executer that have a local reference to Result<DoWork> but i'm passing inherited class, as i know there is no unboxing and boxing with genericls, but is this the right way? i'm working on this because i'm trying to solve the problem of upcasting, so any advice is wellcome

Any object of some class is always also an object of any of its superclasses all the way up in the inheritance tree. Whether you specify that superclass by specifying the name of the superclass, or by a generics parameter doesn't matter.

In both cases, method override still works as expected.

I would recommend a few simple experiments here. Write the class structures, and have a method in all relevant classes that simply prints something unique for each class where you define it. Create the various objects, and call the method. The output then tells you what happened. That instantly tells you if upcasting works as expected.

@Alberth firstly thank's you for help.

actually is what i did, but my question here is this the way to get a better performance instead to simply upcast the DoWork class? in terms of reusability it should be the best way, and as i said i know about the boxing and unboxing ,.. but what about performance? because i can just change all entire module to fit the new generic parameter, or i can simply inject it, and upcast when needed, is the generic better in this case?

Programming is foremost a structuring problem so you can manage the complexity of it. The problem you thus always solve first is to be as clear as possible about capabilities, responsibilities, and working of the code. Your two solutions are not the same in that respect.

If you make a ManagerForDerivedClass class, then the manager knows about all the guts inside DerivedClass. If you make a Manager<SuperClass> class (or ManagerForSuperClass if there is only one relevant SuperClass), the manager doesn't know about all those details, it deals with the objects at a higher level of abstraction. The latter solution clearly signals it is only responsible for stuff at the higher abstraction level, which reduces the complexity of that code, which is good as you want that as low as possible.

There is a second reason why giving access to the superclass only is a good idea for code that doesn't need the details of the derived class. If the manager class doesn't have access to the details of the objects, he cannot use them by accident. The compiler helps you here. When you try to access stuff that only exists in DerivedClass from the Manager<SuperClass> or ManagerForSuperClass, the compiler barks and refuses to compile it. In the ManagerForDerivedClass you will never ever find such cases.

Having the compiler bark at illegal access attempts enforces that the SuperClass definition is actually correct with respect to how you think about the capabilities and responsibilities of SuperClass. You may now think that SuperClass is enough for the manager, but in my experience it is often the case, that you missed one or two edge cases where you do need some information that is not available in SuperClass. A barking compiler tells you about those cases, and gives you the opportunity to find and fix these bugs in the code structure now, rather than after a few months when you stacked heaps of other code around it.

In my view, clarity of code always wins, period. If you care more for performance than me, clarity of code always wins unless there is hard proof it does not. “hard proof” here means you have profile output that shows that Manager<SuperClass> is a lot slower than ManagerForDerivedClass with an analysis that it is not caused by something else (the point where you create a performance issue is usually different from where it pops up). For upcasting, I don't quite buy you're going to have a sufficient big performance problem. Upcasting is one of the corner stones of OOP, so compilers pulled all tricks to make that fast. If you have a performance problem there, you more likely have a structuring problem or a granularity problem somewhere else, but that would need a careful analysis of a concrete case.

@Alberth ok! thank's you! i really appreciate for your help!

so what i care is about the reusability of my code, yes is simple, but i want to skill up and create my own small library to use anytime that i want to create some other game, and is a good start point to learn and test a new things, for what i understand now for reusability generics are better and clean way instead an “injector” but i need to profile it about performance, just for test the both way and learn something, again really thank's you for your help

Performance is quite overrated here in my opinion. It's relevant, but you get the big gains by doing a proper design, use the right data structures at the right time, use efficient algorithms for the problem at hand, and keep in mind how cpu like their memory access to effectively use the caches. That is, write a solid program.

Once you have all that, cpu time is around 80/20. That is, 80% of the cpu time is in 20% of the code. If you do mostly bookkeeping rather than computing something, thus ratio even increases to 90/10 or 95/5. In other words, an extremely small part of the code is ‘hot’. In all other areas, the amount of time spent by the cpu is neglible, so any performance improvement effort there is a waste of time, since even 99% performance improvement of neglible is not even a blip in the profile output. (From 0.01 seconds you go to 0.001 seconds, so the app is almost 0.01 seconds faster!)

The point to spend time on for optimizing is thus the ‘hot’ areas. These are normally very local spots where some inner loop is running. Fixing is often also very local.

Unfortunately, it is not predictable where these hot spots happen. Every time I have a performance problem, for fun I guess where the spot of trouble would be beforehand. Then I measure with profiling. So far in the past 30 years I am 100% consistent in guessing wrong. The problem is never where I expected it, it is also usually something you would never ever consider as a cause for performance problems without profile output.

The quick summary here is, write solid programs (that is hard enough), and don't bother about performance until you have a problem. At that time you have a concrete case, and problems like finding a realistic workload for profiling are much much easier.

This topic is closed to new replies.

Advertisement