Софтуерно Инженерство
Loading...
AntyfrizZz avatar AntyfrizZz 238 Точки

Activator.CreateInstance thrown Exception

Здравейте,

 

EDIT:

Условие + Авторко решение

Моята имплементация

 

Използвам следният код за инстанциране на обекти от дадени класове

IExecutable command = (IExecutable) Activator.CreateInstance(type, args);

 

Абстрактният клас Command изглежда така

 public abstract class Command : IExecutable
    {
        protected string[] arguments;
        protected IDatabase database;

        protected Command(string[] arguments, IDatabase database, int paramethersCount)
        {            
            this.arguments = arguments;
            this.database = database;
            this.ValidateParametersCount(paramethersCount);
        }

        public abstract string Execute();

        private void ValidateParametersCount(int count)
        {
            if (this.arguments.Length != count)
            {
                throw new InvalidOperationException(Constants.InvalidCommand);
            }
        }
    }

 

Има изискване, ако на входа са подадени различен по брой параметри от необходимите, да се хвърли грешка "Invalid Command". Всеки клас, наследник на този, има константно поле с броя на аргументи, които очаква. Подава го на този конструктор и при извикване на конструктора се извиква метода ValidateParametersCount, който сравнява броя на подадените аргументи с броя на очакваните. Ако са различни хвърля InvalidOperationException. Обаче, понеже Activator-а не е успял да инстанцира, той си хвърля друга грешка TargetInvocationException и до Catch-а стига нейното съобщение, а не това на IvalidOperationException.

 

В момента решавам проблема по следния начин, но не ми харесва.

catch (TargetInvocationException)
{
    this.userInterface.WriteLine(Constants.InvalidCommand);
}

 

Въпрос 1. Ок ли е в конструктора на абстрактния клас да правя проверка за броя на аргументите и ако не са равни да хвърлям грешка

Въпрос 2. Как да докарам до catch-а InvalidOperationException, а не TargetInvocationException

 

EDIT: Използвам поста да попитам и защо джъджа не ми харесва решението и гърми с "Compile time error"

Compiled file is missing. Compiler output: C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets(1098,5): error MSB3644: The reference assemblies for framework ".NETFramework,Version=v4.5" were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your assembly may not be correctly targeted for the framework you intend. [C:\Windows\TEMP\kkupuoqr.fxr\Air Conditioner Testing System_Skeleton\BigMani\BigMani.csproj]

 

Поздрави!

Тагове:
1
C# OOP Basics 19/08/2016 12:26:16
stambi4a avatar stambi4a 126 Точки

    Здравей.

    Според мен можеш да си пратиш константите за броя на параметрите в класа Constants, като за всеки климатик сложиш отделна константа, която се използва за валидиране в injector-a или сложиш ValidateParametersCount в CommandInterpreter.

    По въпрос 1, един клас не би трябвало да се самовалидира, ако това не става в  пропъртита или използвани от тях методи. Всяка друга логика се счита че трябва да се валидира отвън, като допълнително изискване към поставената задача - domain logic. Една дискусия по въпроса. Така, че в случая мисля е по-добре да си направиш пропърти.

    По въпрос 2. Къде ти хвърля TargetInvocationEception? Аз разбирам, че го хвърля още при създаването на команди, макар, че според мен рефлекшъна там е ок. Ако все пак го хвърля там, значи имаш проблем с даденият рефлекшън. Ако ти го хвърля при създаване на климатик, значи или си направил грешна валидация за параметри(грешен брой), или другият рефлекшън ти е проблемен. При всички случаи не би трябвало да се стига дотам да хвърля даденият targetinvocationexception.

    Относно Edit-a, без целият код може само да се гадае, но няма да стрелям далеч от целта ако кажа, че ако разрешиш предишните въпроси ще разрешиш и този.

    Още нещо. Защо ти е да инжектваш нещо в конструктора на команда след като всяка команда е еднодневка. Ако изпълняваш командите в commandInterpretera, можеш директно да подаващ string[] arguments и db като параметри.

    Поздрави.

   

1
19/08/2016 03:35:54
AntyfrizZz avatar AntyfrizZz 238 Точки

Здравей,

 

В началото на поста поставих линк към условие, авторско решение и моето решение. Може би трябваше да го направя по - рано.

Както ще видиш в авторското решение, поставени са константи коя команда колко параметъра да очаква. Хубаво, но командите в него се изпъляняват със switch case, което намалява възможността за преизползваемост на кода. Реално в моята имплементация, ако искам да направя валидацията на очакваните параметри извън Comman класа, няма да има нужда от рефлекшъна и при добавяне на нова команда трябва да добавям нов case в switch-а.

По въпроса с пропъртито. Направих го с пропърти, но в моя случай то не се различава много от метода, който изтрих. Реално това пропърти не му трябва гетър, а само един private сетър, който върши работата на метода.

TargetInvocationException хвърляше, когато се опита да създаде команда на която са дадени различен брой от очакваните параметри. Activator.CreateInstance влизаше в конструктора, метода, който проверяваше за валиден брой параметри хвърля InvalidOperationException и поради хвърлената грешка, Activator.CreateInstance не успява да направи инстанция на обект и си хвърля негова грешка. Та до catch тялото стигаше TargetInvocationException. Беше предложено решение да използвам InnerExceptionMessage, което реши проблема. Ако искам рефлекшъна да не хвърля TargetInvocationException, трябва да валидирам параметрите преди да ги подам, което както казах по - горе, не искам да правя.

Edit-а се оправи, като зададох Target framework на проекта да е .NET framework 4.6.1. Не знам защо се оправи, но се оправи.

Колкото до inject-а, всяка команда използва параметри и database. Само една единствена използва един tester, та за нея позлвам inject. За нищо друго.

 

Като обобщение. Струва ли си switch/if за валидация на броя параметри и да не се използва рефлекшът? За нашия случай - ок. 10 команди, ще го преживеем. Но винаги трябва да се програмира с мисълта за по - лесно допълване на кода. Утре клиента ако изиска още 100 команди? Какво правим?

 

Благодаря за отделеното време!

 

Поздрави!

0
stambi4a avatar stambi4a 126 Точки

Отговорите на двата въпроса бяха малко принципни.

Сега като видях кода разбирам, че всъщност имаш 2 опции:

1.Оставяш TargetInvocationReflection. В този случай нямаш контрол над exception-a, nо това не е фатално понеже имаш string[] arguments и db и при всички положения той се хвърля заради грешен брой параметри.

2.Слагаш анотация над командите с пропърти броя параметри, променяш reflection-a да работи така:

                var type =
                this.commands.Values.FirstOrDefault(
                    typ =>
                    typ.GetCustomAttributes(typeof(CommandAttribute))
                        .Any(attr => ((CommandAttribute)attr).ParametersCount == commandArgs.Length));

                if(type == null)
                {
                    throw new InvalidOperationException(Constants.InvalidCommand);
                }

Така ако type  е null хвърляш InvalidOperationException.

Използваният Attribute има пропърти ParametersCount което може да е константа в наследниците. Може да изглежда така:

[AttributeUsage(AttributeTargets.Class)]
    public abstract  class CommandAttribute : Attribute
    {
        protected CommandAttribute(int parametersCount)
        {
            this.ParametersCount = parametersCount;
        }

        public int ParametersCount { get; }
    }

3.Предполагам, че има и други начини с рефлекшън, въпрос на занимавка.

Относно въпроса  дали си струва switch/if, ами той нарушава open-closed принципа, тоест не е кпк.

Като цяло решението ти е доста добро, при проверка предполагам, че това ще помогне да се "недогледат" малки неща като тази проверка.

Поздрави.

1
AntyfrizZz avatar AntyfrizZz 238 Точки

Здравей,

 

Идеята с атрибутите ми допада, но има един доста голям недостатък. Ако не се лъжа, начинът, който предлагаш хваща първатавата срещната команда, която има нужният брой аргументи. Може да имаме повече от една команда, която да изисква съответен брой аргументи.

 

Поздрави!

1
stambi4a avatar stambi4a 126 Точки

Ами да, моя грешка. Когато атрибута и командата имат обща част от името(което си зависи от теб) можеш да добавиш тази валидация, например.

var type =
                this.commands.Values.FirstOrDefault(
                    typ =>
                    typ.GetCustomAttributes(typeof(CommandAttribute))
                        .Any(attr => attr.GetType().Name.StartsWith(commandName) && ((CommandAttribute)attr).ParametersCount == commandArgs.Length));

Примерно: RegisterCarAirConditionerCommand - RegisterCarAirConditionerCommandAttribute

Друг вариант: добавяш пропърти с името на командата в атрибута.

var type =
                this.commands.Values.FirstOrDefault(
                    typ =>
                    typ.GetCustomAttributes(typeof(CommandAttribute))
                        .Any(attr => ((CommandAttribute)attr).Name == commandName && ((CommandAttribute)attr).ParametersCount == commandArgs.Length));

Не съм тествал за всички команди, но примерно за RegisterStationaryAirConditionerCommand работи.

1
19/08/2016 19:48:49
AntyfrizZz avatar AntyfrizZz 238 Точки

Благодаря за идеята с атрибутите. Изглежда ми като по - добрия подход.

1