Страницы

Поиск по вопросам

воскресенье, 30 сентября 2018 г.

Верстка с учетом ppi устройств - как определять, каков подход?

Прошу прощения за картинки-сслыки (не хватает репутации) и за "много букав". О проблеме хочется хотя бы просто поговорить :)
Перелопатив с десяток статей я начал осознавать, что ppi - большая палка в колеса. Как с ним бороться, пока не ясно.
Итак, у нас есть элементарная страница: jsfiddle.net
Мы хотим 14px (к примеру) - как основной размер шрифта. Допустим, что так было в PSD от дизайнера. От этого будем отталкиваться.
Обварачиваем весь контент в div.font-size-setting, которому выставляем font-size: 1.4em (так как после нормализации у нас было 10px).
Смотрим на страницу с разных устройств:

Будь там iphone4 шрифт и квадратик были бы еще меньше. Проблема, очевидно, в большой разнице в ppi (pixels per inch) устройств. Обычные мониторы имеют 96ppi. У iphone3 163ppi. У виты 220ppi
Очевидное решение - это определить ppi устройства и увеличить шрифт. С последним проблем никаких. Обарачиваем в div.font-size-normalize и в зависимости от dpi выставляем соответствующий шрифт: jsfiddle.net
Получаем:
div.font-size-setting - чтобы выставить размер шрифта от дизайнера,
div.font-size-normalize - делаем этот шрифт одинакового размера (визуально) на всех устройствах.

Яблофон чуть растянул сам шрифт или увеличил расстояниями между строчкам, но по сути все сходится. Так или иначе, квадратик с размерами в em везде одинаковый, хоть линейкой меряй.
Вопрос в том, как определять ppi?
1.media queries - resolution Медиа запросы умеют определять не только ширину и высоту, но и много других характеристик, в том числе и dpi (dots per inch). Вообще dpi - бессмысленный параметр для дисплея, так как это параметр для печати принтером, но в нашем случае совпадает с ppi
Я решил, что не плохо было бы сделать шаг в 0.1em. Это примерно каждые 10ppi. Взяв наши 96ppi за 1em составил табличку и посчитал диапазон так, чтобы интересующие нас значения были в середине. Составил запросы:
@media screen and (min-resolution: 90dpi) and (max-resolution: 100dpi) { .font-size-normalize { font-size: 1em; /* normal desktops */ } } @media screen and (min-resolution: 101dpi) and (max-resolution: 111dpi) { .font-size-normalize { font-size: 1.1em; } } @media screen and (min-resolution: 112dpi) and (max-resolution: 122dpi) { .font-size-normalize { font-size: 1.2em; } } /*...*/ @media screen and (min-resolution: 158dpi) and (max-resolution: 168dpi) { .font-size-normalize { font-size: 1.7em; /* iphone3 */ } } /*...*/ @media screen and (min-resolution: 216dpi) and (max-resolution: 226dpi) { .font-size-normalize { font-size: 2.3em; /* ps vita */ } } /*...*/ @media screen and (min-resolution: 322dpi) and (max-resolution: 332dpi) { .font-size-normalize { font-size: 3.4em; /* iphone4 */ } }
http://jsfiddle.net/AHzYJ/3/
Последние версии десктопных браузеров проработали медиазапросы. Браузеры iphone3 и ps vita не осилили. Думаю последние яблофоны, яблопэды, дройдофоны все же справятся, но тем не менее решение далеко не пуленепробиваемо :(
2.media queries - devicePixelRatio и канонический пиксель dip - density(device)-independent pixel - что то вроде канонического пикселя. Равен одному физическому пикселю на 160ppi. С другой стороны мы имеем параметр devicePixelRatio, который может быть использован как аргумент медиа-запроса относительно канонического пикселя:
@media screen and (device-pixel-ratio: 1) {}
Можно написать аналогичное примеру выше множество запросов:
@media screen and (min-device-pixel-ratio: 1) and (max-device-pixel-ratio: 1.1) { .font-size-normalize { font-size: 2.3em; /* ps vita */ } }
Но у меня ни для одного девайса/компьютера/браузера этого толком сделать не получилось. Такое ощущение, что этот параметр может быть только целочисленным: единица - для iphone3, двойка - для iphone4. Более того, требуются вендерные префиксы, что уже как минимум говорит об отсутвии кросс-браузерности или кросс-девайсности. Еще одна несостыковка в том, что "device-pixel-ratio: 2" (означающий 320ppi) отрабатывает на iphone4, обладающий 326ppi
3.JS
alert(window.devicePixelRatio);
Думаю, оно работает точно так же как и второй способ. На iphone3 я увидел "1", так же как в десктопном браузере и на вите, что само собой чушь. На iphone4 высветилось "2", но после предыдущих фэйлов это уже не имеет значение. Еще проблема: тормозилла не справилась и показала undefined
4.Самописные JS-функции, определяющие ppi. Что-то типа взять device-width и поделить на width. Это работает на мобильных устройствах, так как нельзя сплющить окно браузера, но в десктопном браузере мы можем просто уменьшить размер окна и все сломается.
В конечном итоге, я не нашел ни одного полнофункционального решения. Думаю, что просто должно пройти время, пока "device-pixel-ratio: n" будет пониматься всеми. А пока надо придумать как максимизировать количество удачных определений ppi. Ведь описанные выше способы частично, но все же работают.
Кто что думает по этому поводу, какие можетe предложить решения и как можно обобщить вышеуказанные способы в один пусть полурабочий вариант?


Ответ

Спустя какое-то время я пришел к тому, что надо работать по второму предложенному мною способу. Суть рабты примерно следующая. Сайт верстается "как обычно", все размеры задаются в пикселях. Только теперь width: 100px не означает ширину в 100 физических пикселей дисплея. Это 100 некоторых абстрахных пикселей (device independent pixel). А дальше операционная система (или браузер) уже сами решают, как показать один такой абстрактный пиксель на дисплее. Помогает им в этом параметр devicePixelRatio. Если этот параметр равен единице, то абстрактный пиксель показывается один в один с физическим пикселем. Если равен двойке, то используется квадрат из четырех пикселей. Если мы возьмем случайный сайт, вообще не оптимизированный под дисплеи с высокой плотностью пикселя, то он никак не сожмется на ретине. Он будет выгялдеть как на обычном дисплее. Получается, что за размерами следить не нужно. Ширины, высоты, бордюры, тени и прочее можно задавать в обычных пикселях. И не беспокоится о том, что же там будет на ретине. А вот над графикой придется попотеть. Картинки на девайсах с высокой плостностью пикселя выглядят растянутыми. Их надо заменять на картинки увеличенные в соответствующее количество раз. Например. div { background: url(cake.png) no-repeat center top; }
@media screen and (-webkit-min-device-pixel-ratio: 2), screen and ( -moz-min-device-pixel-ratio: 2), screen and ( -o-min-device-pixel-ratio: 2), screen and ( min-device-pixel-ratio: 2) { div { background-image: url(cake@2x.png); background-size: 100%; // или вариации } }

Что такое stack trace, и как с его помощью находить ошибки при разработке приложений?

Иногда при запуске своего приложения я получаю подобную ошибку:
Exception in thread "main" java.lang.NullPointerException at com.example.myproject.Book.getTitle(Book.java:16) at com.example.myproject.Author.getBookTitles(Author.java:25) at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Мне сказали, что это называется «трассировкой стека» или «stack trace». Что такое трассировка? Какую полезную информацию об ошибке в разрабатываемой программе она содержит?

Немного по существу: довольно часто я вижу вопросы, в которых начинающие разработчики, получая ошибку, просто берут трассировки стека и какой-либо случайный фрагмент кода без понимания, что собой представляет трассировка и как с ней работать. Данный вопрос предназначен специально для начинающих разработчиков, которым может понадобиться помощь в понимании ценности трассировки стека вызовов.
Перевод вопроса: «What is a stack trace, and how can I use it to debug my application errors?» @Rob Hruska


Ответ

Простыми словами, трассировка стека – это список методов, которые были вызваны до момента, когда в приложении произошло исключение.
Простой случай
В указанном примере мы можем точно определить, когда именно произошло исключение. Рассмотрим трассировку стека:
Exception in thread "main" java.lang.NullPointerException at com.example.myproject.Book.getTitle(Book.java:16) at com.example.myproject.Author.getBookTitles(Author.java:25) at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Это пример очень простой трассировки. Если пойти по списку строк вида «at…» с самого начала, мы можем понять, где произошла ошибка. Мы смотрим на верхний вызов функции. В нашем случае, это:
at com.example.myproject.Book.getTitle(Book.java:16)
Для отладки этого фрагмента открываем Book.java и смотрим, что находится на строке 16
public String getTitle() { System.out.println(title.toString()); <-- line 16 return title; }
Это означает то, что в приведенном фрагменте кода какая-то переменная (вероятно, title) имеет значение null
Пример цепочки исключений
Иногда приложения перехватывают исключение и выбрасывают его в виде другого исключения. Обычно это выглядит так:
try { .... } catch (NullPointerException e) { throw new IllegalStateException("A book has a null property", e) }
Трассировка в этом случае может иметь следующий вид:
Exception in thread "main" java.lang.IllegalStateException: A book has a null property at com.example.myproject.Author.getBookIds(Author.java:38) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) Caused by: java.lang.NullPointerException at com.example.myproject.Book.getId(Book.java:22) at com.example.myproject.Author.getBookIds(Author.java:35) ... 1 more
В этом случае разница состоит в атрибуте "Caused by" («Чем вызвано»). Иногда исключения могут иметь несколько секций "Caused by". Обычно необходимо найти исходную причину, которой оказывается в самой последней (нижней) секции "Caused by" трассировки. В нашем случае, это:
Caused by: java.lang.NullPointerException <-- root cause at com.example.myproject.Book.getId(Book.java:22) <-- important line
Аналогично, при подобном исключении необходимо обратиться к строке 22 книги Book.java, чтобы узнать, что вызвало данное исключение – NullPointerException
Еще один пугающий пример с библиотечным кодом
Как правило, трассировка имеет гораздо более сложный вид, чем в рассмотренных выше случаях. Приведу пример (длинная трассировка, демонстрирующая несколько уровней цепочек исключений):
javax.servlet.ServletException: Произошло что–то ужасное at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388) at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216) at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182) at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765) at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418) at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152) at org.mortbay.jetty.Server.handle(Server.java:326) at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542) at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943) at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756) at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218) at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404) at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228) at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582) Caused by: com.example.myproject.MyProjectServletException at com.example.myproject.MyServlet.doPost(MyServlet.java:169) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166) at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30) ... 27 more Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity] at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96) at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66) at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822) at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71) at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268) at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321) at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204) at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210) at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195) at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93) at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705) at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693) at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689) at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344) at $Proxy19.save(Unknown Source) at com.example.myproject.MyEntityService.save(MyEntityService.java:59) <-- relevant call (see notes below) at com.example.myproject.MyServlet.doPost(MyServlet.java:164) ... 32 more Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...] at org.hsqldb.jdbc.Util.throwError(Unknown Source) at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source) at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105) at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57) ... 54 more
В этом примере приведен далеко не полный стек вызовов. Что вызывает здесь наибольший интерес, так это поиск функций из нашего кода – из пакета com.example.myproject. В предыдущем примере мы сначала хотели отыскать «первопричину», а именно:
Caused by: java.sql.SQLException
Однако все вызовы методов в данном случае относятся к библиотечному коду. Поэтому мы перейдем к предыдущей секции «Caused by» и найдем первый вызов метода из нашего кода, а именно:
at com.example.myproject.MyEntityService.save(MyEntityService.java:59)
Аналогично предыдущим примерам, необходимо обратить внимание на MyEntityService.java, строка 59: именно здесь появилась ошибка (в данном случае ситуация довольно очевидная, так как об ошибке сообщает SQLException, но в этом вопросе мы рассматриваем именно процедуру отладки с помощью трассировки).
Перевод ответа: «What is a stack trace, and how can I use it to debug my application errors?» @Rob Hruska

Фрагментация памяти

Как известно, сборщик мусора в C# (точнее, в CLR) время от времени проводит чистку оперативной памяти, освобождая память, занятую переменными, которые больше не используются. Кроме этого он также производит дефрагментацию памяти, "уплотняя" кучу.
В связи с этим происходит коррекция ссылок на объекты, пережившие сборку мусора. Вероятно, что-то аналогичное происходит при сборке мусора и в других языках.
В С++ нет сборщика мусора. В таком случае, даже если программист не забудет очистить всю память, выделенную ранее, то ее все равно может оказаться недостаточно из-за фрагментации, так как процесс дефрагментации не проводится.
То есть возможна парадоксальная ситуация, когда общий размер свободной памяти больше, чем требуется для создания нового объекта, но объект не может быть создан. Так ли это? Есть ощущение, что я ошибаюсь в своих рассуждениях, но где?


Ответ

Вопрос очень хороший. И тема интересная и важная. Однако, мне кажется (может быть, просто кажется), что Вы путаете две вещи, точнее, два уровня фрагментации памяти. Память может быть фрагментирована на уровне физической памяти. В системах с виртуальной моделью памяти (а таких сейчас подавляющее большинство) это не проблема, так как даже сильно фрагментированная реальная память будет просто спроецирована на последовательное виртуальное адресное пространство процесса. Другое дело, если фрагментация происходит на уровне виртуальной памяти. Это может запросто произойти в программах на С или С++, где происходит многочисленные выделения и удаления небольших фрагментов памяти. Это может привести к сильной утечки памяти (хотя в коде вся выделенная память освобождается!) и, возможно, к исчерпанию всей системной памяти. Но тут уже всему настанет кердык, если система такие ситуации не отслеживает и не выгружает "прожорливые" процессы.

Как писать простейшие UnitTest'ы к простейшим функциям?

Хочу научиться писать тесты для своих проектов.
Подскажите какие-нибудь хорошие ресурсы, чтобы научиться тестировать Android приложения.
Насколько важно их использование?
Сейчас я пишу приложение без их использования и пока не могу оценить их пользу.
Те коды, которые встречаю в интернете, не помню, чтобы где-то в коде встречал тесты.
В общем, хочу понять, что это значит. Подскажите, с чего начать.
Допустим есть вот такой метод:
private File getFile(File path) { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); return new File(path.getPath() + File.separator + timeStamp + ".html"); }
Как можно к нему написать тест и что нужно для этого сделать?
ПРАВКА
Вот кстати есть ссылка с видео где показан пример теста
https://www.youtube.com/watch?v=ZJE0MDKJOow


Ответ

Для того чтобы создать юнит тест, вам прежде всего нужно определиться что вы собственно собираетесь тестировать. В идеале, ваш метод должен делать что-то одно и тогда ваша задача упрощается. Если возможно, то юнит тест должен тестировать метод как черный ящик, то есть, вы подаете что-то на вход и проверяете полученное значение. К сожалению это не всегда возможно.
В вашем конкретном случае, первое что бросается в глаза, это то что метод помечен как private, такой метод нельзя тестировать стандартными методами. Существует много теорий насчет того нужно ли тестировать приватные методы или нет. Мое мнение - их можно не тестировать. Правильность работы приватных методов будет проверена неявно когда вы тестируете все открытые методы. Хотя если вы очень хотите то можно пометить метод как 'default' (убрать тип доступa), или можно использовать reflection.
Но допустим ваш метод публичный, тогда анализируя код понимаем, что метод возвращает новый файл с именем построенным из пути и функции текущей даты. То есть, он делает два действия: * Создает имя файла * Создает собственно файл Тестировать его в текущем виде можно но не интересно. Я бы посоветовал немного поправить ваш код чтобы он был более пригодным для тестирования:
public File getFileFullName(File path, Date date) { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String fullPath = path.getPath() + File.separator + timeStamp + ".html"; return fullPath; }
// где-то в вашем коде String path = ... Date now = new Date(); String fileFullName = getFileFullName(path, now); File file = new File(fileFullName);
// тест для метода getFileFullName в классе ClassUnderTest @Test public void testGetFileFullName() { String path = "/abc"; Date date = new Date(1465953124); //2016-06-15 13:12:06
ClassUnderTest instance = new ClassUnderTest() String name = instance.getFileFullName(path, date);
assertEquals("/abc/20160615_131206.html"); }
Но вы можете спросить, а как тестировать код где мы создаем дату или используем файл? Касательно даты, я бы рекомендовал вообще не использовать new Date() в коде. Вместо этого использовать что типа провайдера или сервиса для получения текущей даты/времени. Тогда вы можете подменять реализацию этого класса для тестирования. Простейшая реализация может быть синглтон:
public class DateTimeUtils {
private static DateTime fixedTime;
public static DateTime getCurrentDateTime() { if (fixedTime == null) { return new DateTime(DateTimeZone.UTC); } return fixedTime; } public static void useFixedCurrentTime(DateTime timeToReturn) { fixedTime = timeToReturn; } }
Что касается объекта File то здесь вам поможет подмена классов с помощью мокирования. Посмотрите Mockito или PowerMock.
Еще маленькое дополнение. Если бы вы использовали test-driven development то такой проблемы бы не было.

Что такое interning и как им пользоваться

Что такое interning? Для чего оно применяется? Когда стоит его применять и какие возможны подводные камни?


Ответ

Interning — это метод хранения лишь одной копии из многих одинаковых объектов. Применяется в C# и Java к строкам, а также (в Java) к небольшим числам.
Рассмотрим на примере строк. Когда вы говорите string.Intern(s) в C# или s.intern() в Java для строки s, вы получаете строку с таким же содержимым, но возвращённая строка гарантировано одна и та же (то есть, один и тот же объект), если вы запрашиваете интернированную строку с одним и тем же содержимым. Также, строковые константы автоматически интернируются.
Однако, строки полученные другим путём, например, через StringBuilder или конкатенацию, не будут интернированы, по крайней мере в текущей версии языков. (Впрочем, оптимизатор может соптимизировать конкатенацию, если сумеет вычислить аргументы во время компиляции, так что рассчитывать на это не стоит.)
Пример:
// C# object.ReferenceEquals("123", "123") // true object.ReferenceEquals(string.Intern("12" + "3"), "123") // true char[] chars = new[] { '1', '2', '3' }; object.ReferenceEquals(new string(chars), new string(chars)) // false object.ReferenceEquals(new string(chars), "123") // false object.ReferenceEquals(string.Intern(new string(chars)), "123") // true
// Java "123" == "123" // true ("12" + "3").intern() == "123" // true new String("123") == new String("123") // false new String("123") == "123" // false new String("123").intern() == "123" // true
Это значит, что интернированные объекты можно сравнивать через ReferenceEquals (C#) / == (Java).
Когда вызывается метод Intern()/intern(), рантайм-библиотека просматривает пул интернированных объектов в поисках данного или равного ему. Если такой объект находится, он возвращается, если нет, данный объект интернируется и возвращается.

Для чего можно пользоваться этим? Например, можно уменьшить расход памяти программы, если в ней используется большое количество строк, среди которых много дубликатов. Например, у вас есть огромный XML-файл, состоящий из почти одинаковых записей. Или огромный текст программы на каком-нибудь языке программирования. Тогда в некоторых случаях можно уменьшить потребление памяти путём интернирования строк: например, все экземпляры while будут одним и тем же объектом.
Внимание! Сама по себе считанная из файла строка не интернируется, даже если она и равна какой-то интернированной строке.
Учтите, однако, что однажды интернированую строку нельзя «деинтернировать», и она будет занимать память программы даже когда больше не будет вам нужна. Поэтому имейте в виду, что интернирование строк может оказать и негативный эффект на расход памяти программой!
Поэтому если вы решаете применить интернирование в своей программе, обязательно спрофилируйте расход памяти и убедитесь, что ваша оптимизация действительно улучшает ситуацию! (Впрочем, это относится практически ко всем оптимизациям.)
Далее, интернирование строки делает поиск в глобальных структурах, и поэтому наверняка будет требовать глобальной блокировки. Поэтому несколько потоков, активно применяющих интернирование, будут «сражаться» за общий ресурс.
Ещё одним преимуществом интернированных строк является то, что их можно быстрее сравнивать. Например, если вы разбираете программный текст, и все ключевые слова интернированы, вы можете сравнивать их как объекты (что, разумеется, намного скорее).

В .NET вы можете управлять тем, будет ли применяться автоматическое интернирование строковых констант на уровне сборок (assembly). По умолчанию строковые константы, как было сказано выше, интернируются, но вы можете запретить это, указав атрибут CompilationRelaxations.NoStringInterning

В Java кроме строк интернируются также и упакованные (boxed) числа. Например, упакованные константы типов Integer и Long в пределах от -128 до 127, Boolean и Byte хранятся в пуле интернированных объектов. Пример:
Integer x = 1; Integer y = 1; Integer z = new Integer(1);
x == y // true y == z // false

Почему во многих примерах функции называют foo?

Часто вижу в различной литературе, видеоуроках, статьях в интернете и прочем, что демонстративные функции и методы носят название foo. Что это значит ?


Ответ

Скорее всего слова foo, bar и baz родились в комиксе Smokey Stover and Pogo в конце 30-х годов 20-го века (как правильно заметил AnT) и, благодаря своей популярности, стали использоваться технарями из MIT
От автора комикса
What’s Foo? My uncle found this word engraved on the bottom of a jade statue in San Francisco’s China town. The word Foo means Good-Luck. Что такое Foo? Мой дядя нашел это слово выгравированным на дне нефритовой статуэтки в «China town» в Сан Франциско. И обозначает (переводится) оно «удача» или «удачи!»
Вот очень подробный и развернутый ответ на этот вопрос в английской версии StackOverflow. В этом ответе есть воспоминания людей об употреблении слов foobarbazfoobar , которые работали непосредственно в Tech Model Railroad Club (сокращенно TRMC) или, несколько позже, просто в кругу MIT в 1960-1990-х годах.
Также в популяризации данных слов сыграла роль военная аббревиатура FUBAR («Fucked Up Beyond All Repair», что можно перевести как «ремонту не подлежит», что относилось к военной технике, либо «Fucked Up Beyond All Recognition» — речь шла о людских жертвах, которые невозможно опознать), которая появилась во время второй мировой войны и по слухам была придумана неким рядовым, которого «задолбали» всякие военные аббревиатуры.
Технари из TRMC клуба в MIT использовали слово «FOO» для обозначения ситуаций когда была необходима аварийная остановка системы. В случае, когда кто-нибудь нажимал один из аварийных выключателей на системном табло вместо времени появлялась надпись «FOO» и поэтому эти выключатели назвали «Foo switches». Позже в этом клубе стали использовать кнопки с подписями «FOO» и «BAR» (уже как дань традиции), и использовались они в самых разных ситуациях.
Впоследствии это стало использоваться в IT мире как «placeholders», то есть для названия переменных/классов в тех случаях, когда это не важно (например в примерах) или когда на ум ничего лучшего не приходит.
P.S.: также существует неподтвержденная версия, что «FOOBAR» происходит от немецкого «furchtbar» (ужасно).

Как и когда нужно имплементировать IDisposable?

В каком случае мой класс должен имплементировать интерфейс IDisposable? Подскажите правильную имплементацию. Что такое неуправляемые ресурсы, и как нужно оформлять их закрытие?


Ответ

А что это вообще?
Объекты в .NET уничтожаются по своим хитрым правилам. Не как в C++, где объект уничтожается как только выходит из области видимости. В .NET реализована сборка мусора: если на объект нет ссылок, то он считается никому не нужным, и его находит и съедает «сборщик мусора». Причём не сразу, а когда ему заблагорассудится, может даже вообще никогда. [На самом деле, если на объект есть ссылка, но из другого никому не нужного объекта, то эта ссылка как бы не считается.]
Например, если вы обнулите ссылку на объект, это не приведёт к его немедленному уничтожению. (Это вообще не обязательно приведёт к его уничтожению даже потом: на этот объект могут быть и другие ссылки.)
Это может приводить к неприятным эффектам. К примеру, если мы в C++ открыли файл в конструкторе объекта, то мы его можем закрыть в деструкторе. При этом мы точно знаем, когда файл будет закрыт: по окончанию блока, в котором объявлена объект. Если мы откроем файл в конструкторе класса C#, и попытаемся закрыть его в деструкторе, получится плохо: деструктор вызывается лишь тогда, когда сборщик мусора убирает объект, то есть, непонятно когда, и может быть даже вообще никогда. Это значит, что если мы в другой части программы попытаемся открыть снова этот же файл, нам это не удастся: файл может быть всё ещё открыт старым объектом.
Выходит, что деструктор в C# практически бесполезен, и применять его вам придётся очень редко. (Он, кстати, официально называется финализатор.)
Какой выход из этой ситуации? Выход есть, но он требует внимания.
Вы можете объявить метод, который можно вызывать, когда объект должен «умереть». Это делается при помощи интерфейса IDisposable, у которого есть один метод Dispose(). В этом методе и должна происходить «подчистка».
Но этот метод не будет вызван автоматически. Этот метод должны всё равно вызвать вы, система не сделает это за вас. Для системы IDisposable просто ещё один интерфейс, он не имеет никакого особого значения (например, деструктор про него вообще не в курсе). Есть ещё удобная конструкция using, которая вызовет для вас метод Dispose в конце блока:
using (StreamReader r = new StreamReader(path)) { Console.WriteLine(r.ReadLine()); }
это почти то же, что
StreamReader r = new StreamReader(path); Console.WriteLine(r.ReadLine()); r.Dispose();
(но правильно работает в случае исключений и тому подобных штук).
Зачем такой метод, если есть деструктор? Как мы уже выяснили, деструктор вызывается непонятно когда, или вообще никогда. Невозможно вызвать деструктор в подходящий момент. А вот вызвать метод, типа Dispose, в подходящий момент можно.
Когда это нужно?
Начнём с простого примера. Пускай у вас есть в классе поле (ну или свойство), которое имплементирует IDisposable. В этом случае ваш класс тоже должен имплементировать IDisposable, чтобы во время своего Dispose вызвать Dispose и для внутреннего объекта.
Другой случай, когда вам практически всегда нужно реализовывать IDisposable — это если ваш класс работает с WinAPI, и имеет хэндл на внешний объект. Например, вы открываете файл через WinAPI, используя P/Invoke. В этом случае вам нужно вовремя закрыть его, и для этого реализовать IDisposable
Давайте посмотрим, что это значит в общем случае. В нашем классе есть какая-то штука, за которую мы отвечаем, и которую должны прибить в конце существования класса. Такую штуку обычно называют ресурсом
Ресурсы делятся на управляемые (те, которые являются по сути .NET-объектами) и неуправляемые (они обычно являются хэндлами системы и хранятся в IntPtr, но в принципе могут быть любым объектом вне данного рантайма .NET, или даже просто чисто логической сущностью, наподобие права на показ нотификации пользователю). Управляемый ресурс может внутри себя содержать и неуправляемые ресурсы.
Если ваш класс содержит ресурс (например, поле или свойство типа IDisposable или неуправляемый объект WinAPI) вы (скорее всего) должны реализовать IDisposable
Закрытие управляемого ресурса практически всегда сводится к вызову Dispose, т. к. этот ресурс сам должен реализовывать IDisposable. Закрытие неуправляемого ресурса делается специфическим для типа этого ресурса образом.
Если вы пользуетесь неуправляемым ресурсом, превратите его в управляемый ресурс путём упаковки в SafeHandle (об этом ниже). [Разве что у вас есть очень веская причина так не делать. Нет, лень не считается веской причиной.]
Если вы храните ссылку на другие объекты в вашем классе, но эти объекты не имплементируют IDisposable, то вам скорее всего не нужно реализовывать IDisposable самому. Просто память не является ресурсом, который надо освобождать: этим за вас занимается сборщик мусора. Раз вы не можете вызвать Dispose у подобъектов, то они будут освобождены, когда их «съест» сборщик мусора. Поскольку после Dispose ваш объект обычно больше никому не нужен, то он сам скоро будет недоступен, и ссылки из него на подобъекты не будут для сборщика мусора важны, так что обнуление ссылок ничего не даст.
То есть если ваши поля — обыкновенные объекты, не IDisposable (например, строки), то вам скорее всего не нужно реализовывать IDisposable для вашего класса. Если же какой-то один из ваших подобъектов реализует IDisposable, то и вам тоже скорее всего нужно реализовать IDisposable
Реализация IDisposable для случая, когда в вашем классе есть как управляемые, так и неуправляемые ресурсы, сложна и содержит много тонких моментов. Поэтому Microsoft настоятельно рекомендует не пытаться сделать это, а обернуть неуправляемый ресурс в SafeHandle (или другой объект, чьё предназначение — обёртка для ресурса), и на уровне вашего класса работать лишь с управляемыми ресурсами.
Как имплементировать IDisposable правильно?
Если у вас в классе есть неуправляемые ресурсы, сделайте для них управляемую обёртку, как рассказано ниже.
Для случая, когда у вашего класса нет потомков, вы должны просто в Dispose освободить ресурсы. В случае, когда клиент забудет вызвать Dispose, подобъекты вашего класса съест сборщик мусора, и у них (или их подобъектов) вызовется финализатор, который освободит ресурсы. Вашему классу финализатор в этом случае вовсе не нужен.
Вот примерный скелет имплементации (одолжен из этого ответа):
sealed class C : IDisposable { SomeResource1 resource1; SomeResource2 resource2; // тут могут быть ещё ресурсы
bool isDisposed = false;
public C() { try { resource1 = AllocateResource1(); resource2 = AllocateResource2(); } catch { Dispose(); throw; } }
public void Use() { if (isDisposed) // использовать удалённый объект -- ошибка, её лучше проверять throw new ObjectDisposedException("Use called on disposed C"); // ... }
public void Dispose() { // мы уже умерли? валим отсюда if (isDisposed) return; // Dispose имеет право быть вызван много раз
// освободим ресурсы if (resource2 != null) { resource2.Dispose(); resource2 = null; }
if (resource1 != null) { resource1.Dispose(); resource1 = null; }
// и запомним, что мы уже умерли isDisposed = true; } }
Для чего нам try/catch в конструкторе? Если получение ресурса может окончиться неудачей или выбросить исключение, имеет смысл освободить ресурсы сразу, т. к. в этом случае Dispose клиентом вызвано не будет (он не получит ссылку на объект). В случае, если код в конструкторе гарантировано не может выбросить исключение, паттерн можно упростить:
sealed class C : IDisposable { SomeResource1 resource1; SomeResource2 resource2;
bool isDisposed = false;
public C() { resource1 = AllocateResource1(); resource2 = AllocateResource2(); }
public void Use() { if (isDisposed) // использовать удалённый объект -- ошибка, её лучше проверять throw new ObjectDisposedException("Use called on disposed C"); // ... }
public void Dispose() { if (isDisposed) return;
resource2.Dispose(); resource1.Dispose();
isDisposed = true; } }
Для случая иерархии классов метод Dispose в базовом классе нужно объявить виртуальным, и не забыть вызвать base.Dispose() в конце порождённых классов:
class C : IDisposable { // ...
public virtual void Dispose() { if (isDisposed) return; if (resource != null) { resource.Dispose(); resource = null; } isDisposed = true; } }
class C2 : C { // ...
public override void Dispose() { if (isDisposed) return;
if (resource2 != null) { resource2.Dispose(); resource2 = null; } isDisposed = true;
base.Dispose(); } }
Финализаторы и паттерн с Dispose(bool disposing), рекомендуемый FxCop'ом, в этом случае не нужны, т. к. неуправляемых ресурсов наши классы не содержат. Так что предупреждение об этом можно игнорировать.
Как создать управляемую обёртку?
Итак, у нас есть неуправляемый ресурс (то есть, ресурс, не сводимый к .NET-объекту). Нам нужно построить для него IDisposable-обёртку.
Для начала, если наш ресурс — хэндл, то скорее всего у вас уже есть определённый во фреймворке потомок SafeHandle, подходящий для вашего ресурса. Загляните в пространство имён Microsoft.Win32.SafeHandles. В частности, вы можете использовать
SafeFileHandle, SafeMemoryMappedFileHandle, SafePipeHandle для файлов, отображений файлов в память и пайпов (каналов). SafeMemoryMappedViewHandle для представлений памяти. целая группа SafeNCryptKeyHandle, SafeNCryptProviderHandle, SafeNCryptSecretHandle для криптографических функций WinAPI. SafeRegistryHandle для работы с реестром. SafeWaitHandle для хэндлов, по которым можно ожидать чего-нибудь.
Эти классы представляют собой готовые обёртки, вы можете использовать их в определениях P/Invoke. Или если вам не совсем подходит один из готовых классов, вы можете унаследоваться от SafeHandle и получить свою обёртку. Пример отсюда, демонстрирует обе техники:
using System.Runtime.InteropServices.ComTypes; using Microsoft.Win32.SafeHandles;
class FindHandle : SafeHandleZeroOrMinusOneIsInvalid { private FindHandle() : base(true) { } protected override bool ReleaseHandle() { return FindClose(this); } }
[DllImport("kernel32.dll")] static extern bool FindClose(FindHandle handle); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] struct DATA // WIN32_FIND_DATA { public FileAttributes FileAttributes; public FILETIME CreationTime, LastAccessTime, LastWriteTime; public uint FileSizeHigh, FileSizeLow; public uint Reserved0, Reserved1; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string FileName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] public string AlternateFileName; } [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern FindHandle FindFirstFileEx(string name, int i, out DATA data, int so, IntPtr sf, int f); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern bool FindNextFile(FindHandle h, out DATA data);
Если вам нужно создать собственную обёртку, а готовые базовые классы не подходят, подойдёт такой костяк:
class CustomResourceHolder : IDisposable { IntPtr resource; bool isDisposed = false;
public CustomResourceHolder() { resource = AllocateUnmanagedResource();
// эта строчка нужна в конце конструктора, иначе финализатор может начать // есть объект до окончания работы конструктора! GC.KeepAlive(this); }
public IntPtr GetHandleDangerous() { return resource; }
public void Dispose() { DoDispose(); // эта строка гарантирует, что объект будет считаться достижимым // до конца DoDispose, и что в случае Dispose финализатор вызван не будет GC.SuppressFinalize(this); }
~CustomResourceHolder() { DoDispose(); }
void DoDispose() { if (isDisposed) return; // идемпотентность Dispose
// в любом случае освободим ресурс // нам нужна понадобиться проверка того, а был ли реально аллоцирован // ресурс (например, его выделение могло бросить исключение) if (resource реально был выделен) FreeUnmanagedResource(resource);
// и запомним, что мы уже умерли -- это должно быть последней строкой isDisposed = true; } }

Подстрочное примечание для знатоков: зачем нужен GC.KeepAlive(this) в конце конструктора? Нам нужно гарантировать, что финализатор не начнёт выполняться до окончания конструктора. (Он может! См. тут [раздел «Myth: An object being finalized was fully constructed»] и тут). Тело конструктора в реальном коде может быть сложнее, и гарантии, что последним в конструкторе будет обращение к this, а не работа с полученным ресурсом, достаточно сложно, и требует отдельных усилий. (Свою долю сложности привносит и оптимизатор, который может переставить куски кода.)
Использование GC.KeepAlive(this) в конце конструктора позволяет избавиться от этих проблем наиболее простым способом.

Огромное спасибо @PashaPash, @Stack, @Discord, @Pavel Mayorov и @i-one, которые своей конструктивной критикой и рекомендациями очень помогли улучшить ответ.