.NET

индекс
121,03

C#: Этюды, часть 3

В предыдущей части было предложено три разных по своей сути решения.

Сегодня — новая загадка, из области ASP.NET. Её основное отличие от предыдущих в том, что я столкнулся с ней в реальной ситуации, и пришлось потратить время, чтобы разобраться, в чем же дело.

Итак, на странице Test.aspx имеется код:



protected void Page_Load(object sender, EventArgs e)
{
  try
  {
    if (object.ReferenceEquals(Request["ID"], "aaa"))
    {
      Response.Redirect("~/PageA.aspx");
    }
    else if (Request["ID"] == "bbb")
    {
      Response.Redirect("~/PageB.aspx");
    }
    else
    {
      int i = int.Parse(Request["ID"]);
      object boxedI = i;
      if ((i > 5) && ((long)boxedI == 10))
      {
        Response.Redirect("~/Page10.aspx");
      }
      Response.Redirect("~/PageDafault.aspx");
    }
  }      
  catch (Exception)
  {
    Response.Redirect("~/Error.aspx");
  }
}

* This source code was highlighted with Source Code Highlighter.

Также имеются четыре ссылки:
  1. Test.aspx?ID=aaa
  2. Test.aspx?ID=bbb
  3. Test.aspx?ID=4
  4. Test.aspx?ID=10


UPD Итак, правильные ответы:
Вопрос на «тройку»: куда приведет каждая из них? >>>Любая ссылка приведет на Error.aspx<<<

Вопрос на «четверку»: почему именно так? >>>Response.Redirect прерывает поток выполнения кода страницы, бросая для этого ThreadAbortException. catch(Exception) ловит его, бросая опять в Response.Redirect, но направляя при этом на другую страницу<<<

Вопрос на «пятерку»: как Вы предлагаете исправить ситуацию? >>>Больше всего мне понравился способ wdk: просто перехватить нужный тип исключения перед catch(Exception): habrahabr.ru/blogs/net/77154/#comment_2246114 Несмотря на всю простоту этого кода он делает именно то, что нужно: прерывает выполнение кода страницы. Поскольку исключение ThreadAbortException регенерируется в конце блока catch, это не приведет к выполнению ненужного кода за пределами блока try и в других событиях страницы. А решение habrahabr.ru/blogs/net/77154/#comment_2245598 как раз приводит к такому выполнению, поэтому мне и не нравится<<<

Похоже, выбранный мной формат загадок не совсем подходит для блога .NET, поэтому ищите следующие загадки у меня в блоге :)

Спасибо всем!
+7
3 декабря 2009, 14:58
6

комментарии (76)

–1
wdk #
aaa, очевидно, проверку не пройдёт, поскольку ссылки фиг совпадут, потом на Parse вылетит ошибка
bbb — всё нормально, на PageB
0
Smerig #
почему на Parse вылетит ошибка?
–1
wdk #
Потому что «aaa» не шибко-то похоже на int в десятичной системе. Нужно юзать перегруженный Parse с заданным NumberStyles, если хочется сравнивать с 0xAAA, либо TryParse, если «aaa» — всё же строка.
–1
Smerig #
а вообще, есть тоже один нюанс. Если зайти на страницу тестовую, то вылетит на parse ошибка, это да, потому что в querystring будет пусто. Соответственно перекинет на Error.aspx
0
iaroshenko #
ссылки идут с другой страницы (например, с Default.aspx)
ID указан в том виде, что я описал
0
Ordos #
В querystring возможно, но Request[«ID»] также ищет и в куках, форме и серверных переменных.
0
Smerig #
сначала ищет в кверистринг, так что тут все нормально
0
Smerig #
а, я Вас понял :) извиняюсь
0
iaroshenko #
к сожалению, ответ неправильный
0
PingWin #
А как же интернирование строк? :)
–1
wdk #
C «4» всё понятно, && не пускает до ошибочного кода, и редирект на дефолт.
10 — int занимает в памяти меньше, чем long, и привести не получается (вроде так).
0
iaroshenko #
тоже неверно.
пришлось апдейт написать для минусующих, а то уже вылетел из захабреных (
0
wdk #
Не переживайте, это не я.
0
iaroshenko #
и на том спасибо
+4
Ordos #
Пошли вопросы аля «Я тут написал хрен знает сколько строк кода, а вы без компилятора попробуйте определить, что выведет программа и где именно я спрятал подводный камень». Это уже не интересно. Если задаёте вопросы, то задавайте их по существу.
0
iaroshenko #
подводный камень там настолько большой, что я попытался его прикрыть веточками…
пока что хабраюзеры видят только эти веточки
0
iaroshenko #
кстати, компилятором пользоваться я не запрещал
0
Smerig #
я, может, совсем чепуху скажу, не пинайте меня, ибо C# я знаю не так глубоко.

Я сейчас попытаюсь объяснить, почему нельзя распаковать boxedI. Когда упаковываем int, создаётся объект, под него выделяется память, нужная для зранения int, когда же мы распаковываем в long без создания переменной, то у нас не хватает памяти, поэтому выпадает эксепшн. Прошу гуру C# не сильно смеяться, если чепуху сказал :)
0
iaroshenko #
дело не в нехватке памяти, наоборот, long длинее чем int. память для временного long выделяется на стеке или в регистре, так что с памятью все в порядке.
ошибка возникает просто из-за несоответствия типов — боксированная переменная содержит ссылку на объект-тип int, а не long, поэтому приведение не срабатывает
0
Smerig #
не знаю, у одного у меня так, или это и есть камень, внутри блока try catch не работают редиректы. Чтобы это исправить, можно отказаться от трайкеч, при парсинге использовать tryparse, при сравнении с aaa использовать просто строки (да и вообще можно везде поставить switch). Если не нашли пути, то переходим на error.aspx
0
iaroshenko #
отказаться от try/catch не всегда возможно, хорошо если есть аналог Parse-TryParse. а если нет?
0
Smerig #
а нет только во фреймворке 1.1 и ниже. В другом случае, можно позаимствовать идею из этих методов. Благо, .net reflector существует :)
0
iaroshenko #
я имею в виду, если приходится вызывать не int.Parse(), а MyClass.GetValue(), у которого нет аналога с Try…
0
Angelina_Joulie #
Исправляеться вот так:
long b = Convert.ToInt64(box);

вот так:
long b = (long)(int)box; //не желательно, т.к. мы точно не знаем, что там long/int? итд.

if (object.ReferenceEquals(Request[«ID»], «aaa»)) //true, if ID=«aaa» т.е. сравнение для строк проходит иначе чем для всех остальных объектов.

Просто пример:
var a = «aaa»
object.ReferenceEquals(a, «aaa»); //true
но
a += «a»;
object.ReferenceEquals(a, «aaa»); //false
причина:
у строк оператор += перегружен и он создаст новый экземпляр.

к тому же .NET строки ещё и кэширует.

Ответ:
~/PageA.aspx (присание выше)
~/PageB.aspx //всё ок, если не вдаваться в подробности культурологии и прочего
~/Error.aspx //Invalid Cast Exception
~/Error.aspx //Invalid Cast Exception

0
Angelina_Joulie #
описание*
0
iaroshenko #
решение проблемы более-менее годится, но вы решаете не ту проблему
ответ на вопрос «на тройку» неправильный
+2
Exorcist #
Всегда будет перехдить по Response.Redirect("~/Error.aspx"); Т.к. какой то из методов всегда генерирует исключение. Которое перехватывает наш try{}catch{}
Не так давно в каком то тематическом блоге данная ситуация разбиралась.
0
iaroshenko #
да, ответ на тройку правильный )
+2
outcoldman #
false поставьте в Response.Redirect("...", false). Проблема эта часто описывалась.

Вообще мне честно говоря, эти этюды кажется не очень полезными сообществу — сам заглядываю, но не голосую.
0
iaroshenko #
видимо, не так часто, поскольку на хабре я не нашел нужного топика, и как видите, пока еще никто не ответил, почему так
а Ваше предложение тоже не очень устраивает, потому что тогда будут возникать события контролов типа OnChange и т.д. Я же когда пишу Redirect, хочу этого избежать
0
outcoldman #
Не в тех кругах общаетесь. :) Попробуйте зайти на GotDotNet и найти там проблему эту.
0
iaroshenko #
это такой тонкий пиар? )
решение проблемы можно найти где угодно, но главное — понять принцип, почему данный код работает именно так
а решение с false все еще не устраивает )
0
outcoldman #
:) а я и не собираюсь делать, чтобы кого-то что-то устраивало. Пиарить GotDotNet, думаю, не нужно, про него и так все хорошо знают. Вот еще советы:
Если хотите продолжать, делайте топик хотя бы закрытый. Как думаете, если тут php-разработчики сидят, они хотят видеть такие «этюды» каждый день? А если по каждому языку такой «этюд»?
Для меня таких «этюдов» море на GotDotNet, я лучше потрачу свои силы, чтобы действительно народу помочь, а не поломать мозг в соревновании у кого длиннее ;)
0
iaroshenko #
кстати, насчет полезности…
что было бы полезнее, на Ваш взгляд?
0
outcoldman #
Не знаю. Может и это полезно. Например, этот «этюд» и меня заинтересовал.
В любом случае — делайте их по крайней мере внутри .net блога. Так тут будут только заинтересованные.
0
iaroshenko #
*в растерянности*
а я где сделал?
0
outcoldman #
имел ввиду закрытый, о чем написал тут
0
iaroshenko #
понял, спасибо
простите новичка )
0
iaroshenko #
а эти закрытые топики будут видны только залогиненым пользователям хабра? или всем, кто зайдет в блог .NET?
0
outcoldman #
«Закрытый хабратопик, будет виден только подписчикам блога.» — только зарегистрированным
0
iaroshenko #
это не очень хорошо, учитывая отсутствие свободной регистрации на хабре…
0
outcoldman #
По-другому у вас, наверное, не получится.
0
iaroshenko #
можно было бы задать в качестве следующего этюда )
пока я вижу только одно решение — дублировать в .NET в закрытом режиме и в персональном блоге — в открытом. Приемлемо ли такое на хабре?
0
outcoldman #
Не видел такого. Я вообще не «бывалый» хабра. Это чисто мои рекомендации :)
+1
Smerig #
многих и персональный лог не устроит, ибо он тоже может попасть на главную. В списке новых топиков он появтся. Продолжайте транслировать в блог .Net, ничего плохого в том, что в дотнете появляются новые интересные топики, я не вижу. :)
0
iaroshenko #
по крайней мере, в персональном блоге я буду чувствовать себя свободнее и писать то, что считаю нужным
спасибо за поддержку )
0
dekko #
1. Test.aspx?ID=aaa — ~/PageA.aspx (сравниваются значения string)
2. Test.aspx?ID=bbb — ~/PageB.aspx
3. Test.aspx?ID=4 — ~/PageDafault.aspx (из-за первого условия в if)
4. Test.aspx?ID=10 — ~/Error.aspx (ошибка анбоксинга, long не int, должно быть точное соответствие)

а вот исправление — это больше вопрос философский :) если просто исправить для 10, то поменять long на int — просто не будет редиректа на Error.aspx. А все остальное — это от стремления к прекрасному зависит.

PS. в asp.net вообще ноль.
0
iaroshenko #
ответ неверный
конечно, знания ASP.NET тут играют существенную роль, но можно попытаться и общими методами догадаться
что по-Вашему делает Redirect и как бы Вы его написали, если бы Вам пришлось это делать?
0
dekko #
Почитал про Redirect. Именно из-за него все будет идти в Error.aspx. А решение — я пока не знаю то, чего хотят :)
0
iaroshenko #
хочу, чтоб код работал так, как изначально предполагало большинство хабровчан (опуская детали), но с минимальными изменениями
+2
wdk #
Ну, если с минимальными изменениями, можно забить на это исключение
catch (ThreadAbortException)
{
}
catch (Exception)
{
Response.Redirect("~/Error.aspx");
}
0
iaroshenko #
да, замечательно )
0
mnarinsky #
Если я не ошибаюсь при таком решении сработает только запрос Test.aspx?ID=bbb (приведет на страницу "~/PageB.aspx")

В остальных случаях будет немного иначе:

— Test.aspx?ID=aaa приведет на Error.aspx — сравнение (object.ReferenceEquals(Request[«ID»], «aaa»)) естественно даст false и код соотвественно попадет на int i = int.Parse(Request[«ID»]), где выскочит FormatException.

— Test.aspx?ID=10 тоже приведет на Error.aspx — на месте ((long)boxedI == 10 выскочит InvalidCastException.

— Test.aspx?ID=4 приведет на PageDefault.aspx — тут немного интереснее. Вообще по аналогии с предыдущем запросом должна также сработать InvalidCastException, но так как первая часть условия (i > 5) при сравнении с 4 даст false, то в связи со спецификой оператора && (сокращенное вычисление) вторая часть условия проверяться вообще не будет. Если заменить строку сравнения на
((i > 5) & ((long)boxedI == 10)) — то выдаст ожидаемую InvalidCastException



Мне кажется по логике задачи ожидалась несколько другая функциональность.
0
iaroshenko #
логику Вы описали безупречно, но она надуманная, и предназначена, чтобы люди отвекли свое внимание и не заметили настоящего подвоха )
собственно, интернирование строк, неявное/явное приведение типа и короткая схема вычисления логических выражений — отвлекающий маневр, не более
смотрите ветку сразу после этой
0
dmodeus #
Не понимаю я этих сферических коней в вакууме — имхо, лучше разбор конкретной строки, которая представляет наибольший интерес, нежели код, от которого так и хочется воскликнуть — «А нахрена?!».
0
iaroshenko #
тогда это будет похоже не на загадку, а на лекцию…
0
dmodeus #
Мне не по душе такой загадкий код. Хотя не спорю, многим задание может показаться интересным.
0
iaroshenko #
мне-то оно точно было интересным, когда я столкнулся с таким поведением )
а все запутывания помогают лучше передать реальную ситуацию
вот если бы я написал

try {Response.Redirect("~/PageA.aspx"); }
catch(Exception) {Response.Redirect("~/Error.aspx"); }

тогда бы конь был более сферическим))
0
wdk #
Запустил таки код. Всегда на error.
Вообще говоря, я не понимаю, нафига этот квест с заведомо отвлекающей хернёй типа ReferenceEquals и боксинга, если решение по типу исключения гуглится в минуту.
0
iaroshenko #
ну кстати, посмотрите сколько на ReferenceEquals было вариантов, причем неправильных
конечно, я овлекающие вещи поставил, потому что не хотел, чтоб ответ через минуту появился )
0
kazkad #
Я написал себе вот такой класс:

public class QueryString
{
static public bool HasParam(string Name)
{
return Get(Name).Length > 0;
}

static public bool Equal(string Name, string Value)
{
return Get(Name).ToLower() == Value.ToLower();
}

static public string Get(string Name)
{
return Get(Name, "");
}

static public string Get(string Name, string DefaultVal)
{
string retVal = HttpContext.Current.Request.QueryString.Get(Name);

if (null == retVal)
retVal = DefaultVal;

return retVal;
}

static public int GetInt(string Name)
{
return GetInt(Name, 0);
}

static public int GetInt(string Name, int DefaultVal)
{
string strVal = Get(Name);
int val = DefaultVal;

if (!int.TryParse(strVal, out val))
val = DefaultVal;

return val;
}

static public long GetLong(string Name, long DefaultVal)
{
string strVal = Get(Name);
long val = DefaultVal;

if (!long.TryParse(strVal, out val))
val = DefaultVal;

return val;
}
}

Usage
protected void Page_Load(object sender, EventArgs e)
{
int Id = QueryString.GetInt(«id», -1);

или

return QueryString.Equal(«mode», «edit»);
0
iaroshenko #
да, многие так делают
но проблема совсем не в этом )
это скорее для отвлечения внимания
0
kazkad #
а зачем выдумывать такой код как в топике?
0
iaroshenko #
можете почитать в этой ветке: habrahabr.ru/blogs/net/77154/#comment_2246086
0
ZlobnyiSerg #
Ситуация с aaa приведет к ошибке, поскольку первый if не сработает. ReferenceEquals работает только для интернированных одинаковых строк, которое осуществляется на этапе компиляции. В указанном случае константа «aaa» и Request[«ID»] — слишком разные вещи
bbb приведет на PageB.aspx — сработает простое сравнение, т.к. для строк оператор перекрыт на Equals
4 уведет на PageDefault.aspx (поскольку в if сработает первое сравнение, i>5 — а это ложь, до второго дело не дойдет)
10 из инта уведет на ошибку потому что нелья боксить один тип а анбоксить другой (точно ошибку не помню)
0
iaroshenko #
если это ответ на самый первый вопрос, то он неправильный
хотя насчет интернирования, сравнения, короткой схемы логики и анбоксинга Вы правы )
кстати, интернировать можно и явно, а не только на этапе компиляции, но я почти уверен, что Вы это тоже знаете
+1
ZlobnyiSerg #
Перечитал предыдущих комментаторов и потому повнимательнее посмотрел на исходный код — действительно я увлекся решением не совсем той задачи. На самом деле Response.Redirect вызовет Thread.Abort, потому во всех случаях будет брошено исключение ThreadAborted и сработает редирект на Error.aspx.

А вообще думаю этюды действительно несколько бесполезны — они предполагают наличие некоторых очень специфических знаний (или отсутствия лени для «ковыряния» специфики), которые даже для опытного программиста вовсе не обязательны.

Знаете, мне пришлось за последний месяц прособеседовать очень много людей на позицию старших/ведущих разработчиков C# (с упором на знание ASP.NET), так вот практика показала, что даже намного более простые задачи для 90% можно считать «этюдами» и задачами требующими очень много времени на решение :)
Может лучше ввести практику на хабре подбрасывать более-менее стандартные задачи и поощрять за наиболее красивое, изящное, ошибостойкое и правильное решение? :)
0
iaroshenko #
ну допустим, Вы считаете эти знания необязательными, я же придерживаюсь противоположной точки зрения )

как показала практика моих этюдов ), на хабре мои задачи решают довольно быстро, так что не вижу смысла здесь давать более простые. наоборот, стоит повышать уровень здесь сидящих, а не понижать

хотя если Вы уверены в своей задумке — никто не мешает попробовать ))
0
lostmsu #
Отсюда мораль — никогда не используйте catch(Exception).
0
lostmsu #
А я ещё удивлялся, зачем на работе в коде встречается catch(ThreadAbortException).
0
iaroshenko #
в общем случае согласен, но бывают и частности
например, писать 10 раз catch чтоб в каждом редирект на Error.aspx — тоже не очень красиво
0
lostmsu #
Для этого есть custom error page и Global.aspx
0
iaroshenko #
кстати, они в том проекте тоже используются, и даже правильно работают
здесь ситуация была в особенной логике для одной страницы
0
lostmsu #
В любом случае, уверен, можно обойтись без этого.
0
shitware #
у меня тоже есть задачка для вас уважаемые
вопрос на тройку:

int a = 12;
int b =32;

Response.Write((a * b).ToString()); каков результат? гггг

вопрос на 3+ ;)

Каков результат компиляции и выполнения приведенного ниже кода?

static void Main(string[] args)
{
Console.WriteLine(GetSomeResult(10000));
}

static long GetSomeResult(long someValue)
{
long value1 = 10 * 1000 * 10000 * someValue;
long value2 = 10 * 1000 * 10000 * 100000;
return value2 / value1;
}
+1
iaroshenko #
ну второй известный прикол, все Ваши константы типа int, потому будет переполнение, несмотря на то, что тип переменной long
насчет результата компиляции не уверен, константы вычисляются в это время, но работатет ли там соответствующая опция — не знаю
исправить можно либо так: long value2 = 10L * 1000 * 10000 * 100000;
либо так: long value2 = unchecked(10 * 1000 * 10000 * 100000);
зависит от того, что именно Вам требуется

в первом подвоха не увидел, к сожалению (

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.