Възможно ли е да се преработи този метод на разширение?

Имам следния метод за разширение:

public static void ThrowIfArgumentIsNull<T>(this T value, string argument) 
    where T : class
{
    if (value == null)
    {
        throw new ArgumentNullException(argument);
    }
}

и това е пример за използването му....

// Note: I've poorly named the argument, on purpose, for this question.
public void Save(Category qwerty)
{
    qwerty.ThrowIfArgumentIsNull("qwerty");
    ....
}

работи 100% добре.

Но не ми харесва как трябва да предоставя името на променливата, само за да помогна на моето съобщение за изключение.

Чудех се дали е възможно да се преработи методът за разширение, така че да може да се извика така...

qwerty.ThrowIfArgumentIsNull();

и автоматично установява, че името на променливата е "qwerty" и следователно го използва като стойност за ArgumentNullException.

Възможен? Предполагам, че отражението може да направи това?


person Pure.Krome    schedule 09.12.2009    source източник
comment
вижте също stackoverflow .com/questions/869610/   -  person Ian Ringrose    schedule 09.12.2009
comment
Проверете msmvps.com /blogs/jon_skeet/archive/2009/12/09/ - публикуван в блог :)   -  person Jon Skeet    schedule 09.12.2009
comment
Мисля, че това е много трудно решение на много прост проблем... Ако използвате Visual Studio, можете да използвате кодови фрагменти, за да направите това много лесно;)   -  person Jeroen Landheer    schedule 09.12.2009
comment
@Jeroen: Изглежда предполагате, че проблемът е свързан с писането - вместо да бъдете устойчиви на рефакторинг и да поддържате минимално количеството глупости в кода.   -  person Jon Skeet    schedule 10.12.2009
comment
вижте също stackoverflow.com/questions/669678/   -  person Ian Ringrose    schedule 02.03.2010


Отговори (6)


Не, не можеш да направиш това. Би било хубаво, но не е възможно без да се включи някакъв вид AOP. Сигурен съм, че PostSharp може да свърши добра работа, надявам се, че използва атрибути, а в Code Contracts ще бъде просто:

Contract.Requires(qwerty != null);

В идеалния случай бих искал атрибут PostSharp, който генерира извикването на Code Contracts - и ще си поиграя с това в даден момент - но дотогава методът за разширение, който имате, е най-добрият подход, който открих...

(Ако някога опитам подхода PostSharp + Code Contracts, със сигурност ще напиша блог за него, между другото... Mono Cecil също може да го направи сравнително лесно.)

РЕДАКТИРАНЕ: За да разширите отговора на Лоран, потенциално бихте могли да имате:

new { qwerty }.CheckNotNull();

И ако имахте много ненулеви параметри, бихте могли да имате:

new { qwerty, uiop, asdfg }.CheckNotNull();

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

РЕДАКТИРАНЕ: Кодът е внедрен и публикация в блог надлежно направени. Ик, но забавно.

person Jon Skeet    schedule 09.12.2009
comment
Прочетох публикацията в блога ви и честно казано не е нужно да преминавате през цялата тази олелия. Просто имплементирайте ThrowIfNull() метод без параметри и оставете разработчика на stack-trace toting да се разходи малко по стека, за да разбере кой аргумент е нула. Просто мисъл :) - person RCIX; 10.12.2009
comment
@RCIX, когато хвърляте ArgumentNullException, от вас се изисква да кажете името на параметъра. Ако OP не се интересуваше от стандарта, това нямаше да е проблем. Моля, вижте Указанията за проектиране на API: msdn.microsoft.com/library /en-us/cpgenref/html/ - person Jared; 16.12.2009
comment
@Jared: Разгледах както раздела за повдигане и обработка на грешки, така и класа ArgumentNullException и не видях никакво споменаване на това. - person RCIX; 21.12.2009

С една дума: не.

На метода за разширение се предава стойност. Той няма представа откъде идва стойността или какъв идентификатор може да е избрал повикващият да я назове.

person AnthonyWJones    schedule 09.12.2009

Смятам, че е най-лесно да направя това с помощта на кодов фрагмент.

Във вашия пример мога да напиша tna<tab>qwerty<enter>.

Ето фрагмента:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
                <Title>Check for null arguments</Title>
                <Shortcut>tna</Shortcut>
                <Description>Code snippet for throw new ArgumentNullException</Description>
                <Author>SLaks</Author>
                <SnippetTypes>
                        <SnippetType>Expansion</SnippetType>
                        <SnippetType>SurroundsWith</SnippetType>
                </SnippetTypes>
        </Header>
        <Snippet>
                <Declarations>
                        <Literal>
                                <ID>Parameter</ID>
                                <ToolTip>Paremeter to check for null</ToolTip>
                                <Default>value</Default>
                        </Literal>
                </Declarations>
                <Code Language="csharp"><![CDATA[if ($Parameter$ == null) throw new ArgumentNullException("$Parameter$");
        $end$]]>
                </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>
person SLaks    schedule 09.12.2009

Вижте също ArgumentNullException и рефакторинг за пълен решения по същите линии като отговора.

Какво относно:

public void Save(Category qwerty)
{   
   ThrowIfArgumentIsNull( () => qwerty );
   qwerty.ThrowIfArgumentIsNull("qwerty");    
   // ....
}

след това дефинирайте ThrowIfArgumentIsNull като

public static void ThrowIfArgumentIsNull(Expression<Func<object>> test)
{
   if (test.Compile()() == null)
   {
      // take the expression apart to find the name of the argument
   }
}

съжалявам, че в момента нямам време да попълня подробности или да предоставя пълния код.

person Ian Ringrose    schedule 09.12.2009
comment
Не се нуждаете от частта return в ламбда. - person R. Martinho Fernandes; 09.12.2009

Препоръчвам ви да направите следното:

public static void ThrowIfArgumentIsNull(this object value, string argument) 
{
    if (value == null)
    {
        throw new ArgumentNullException(argument);
    }
}

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

person Jaco Pretorius    schedule 09.12.2009
comment
Използването на генерични данни позволява на метода да изключва типове стойности. Обърнете внимание на where T : class - person Greg; 10.12.2009

Харесвам Enforce от споделени библиотеки Lokad.

Основен синтаксис:

Enforce.Arguments(() => controller, () => viewManager,() => workspace);

Това ще хвърли изключение с името и типа на параметъра, ако някой от аргументите е нула.

person Nick Cox    schedule 11.12.2009