Skip to content

Commit

Permalink
[core] use StringComparer for Dictionary/HashSet
Browse files Browse the repository at this point in the history
Context: dotnet#12130
Context: https://github.com/angelru/CvSlowJittering

When profiling the above customer app while scrolling, 4% of the time
is spent doing dictionary lookups:

    (4.0%) System.Private.CoreLib!System.Collections.Generic.Dictionary<TKey_REF,TValue_REF>.FindValue(TKey_REF)

Observing the call stack, some of these are coming from culture-aware
string lookups in MAUI:

* `microsoft.maui!Microsoft.Maui.PropertyMapper.GetProperty(string)`
* `microsoft.maui!Microsoft.Maui.WeakEventManager.AddEventHandler(System.EventHandler`1<TEventArgs_REF>,string)`
* `microsoft.maui!Microsoft.Maui.CommandMapper.GetCommand(string)`

Which show up as a mixture of `string` comparers:

    (0.98%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.OrdinalComparer.GetHashCode(string)
    (0.71%) System.Private.CoreLib!System.String.GetNonRandomizedHashCode()
    (0.31%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.OrdinalComparer.Equals(string,stri
    (0.01%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.GetStringComparer(object)

In cases of `Dictionary<string, TValue>` or `HashSet<string>`, we can
use `StringComparer.Ordinal` for faster dictionary lookups.

Unfortunately, there is no code analyzer for this:

dotnet/runtime#52399

So, I manually went through the codebase and found all the places.

I now only see the *fast* string comparers in this sample:

    (1.3%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.OrdinalComparer.GetHashCode(string)
    (0.35%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.OrdinalComparer.Equals(string,stri

Which is about ~0.36% better than before.

This should slightly improve the performance of handlers & all
controls on all platforms.

I also fixed `Microsoft.Maui.Graphics.Text.TextColors` to use
`StringComparer.OrdinalIgnoreCase` -- and removed a
`ToUpperInvariant()` call.
  • Loading branch information
jonathanpeppers committed May 2, 2023
1 parent 85625dd commit 77de00e
Show file tree
Hide file tree
Showing 34 changed files with 46 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public partial class BlazorWebViewHandler : ViewHandler<IBlazorWebView, NWebView
})();
";

static private Dictionary<string, WeakReference<BlazorWebViewHandler>> s_webviewHandlerTable = new Dictionary<string, WeakReference<BlazorWebViewHandler>>();
static private Dictionary<string, WeakReference<BlazorWebViewHandler>> s_webviewHandlerTable = new(StringComparer.Ordinal);

private TizenWebViewManager? _webviewManager;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal static string GetResponseContentTypeOrDefault(string path)
: "application/octet-stream";

internal static IDictionary<string, string> GetResponseHeaders(string contentType)
=> new Dictionary<string, string>()
=> new Dictionary<string, string>(StringComparer.Ordinal)
{
{ "Content-Type", contentType },
{ "Cache-Control", "no-cache, max-age=0, must-revalidate, no-store" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public override void SetVideoURI(global::Android.Net.Uri uri, IDictionary<string

public override void SetVideoURI(global::Android.Net.Uri uri)
{
GetMetaData(uri, new Dictionary<string, string>());
GetMetaData(uri, new Dictionary<string, string>(StringComparer.Ordinal));
base.SetVideoURI(uri);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public abstract class VisualElementRenderer<TElement> : IVisualElementRenderer,
{
event EventHandler<VisualElementChangedEventArgs> _elementChanged;

readonly Dictionary<string, Action<bool>> _propertyHandlersWithInit = new Dictionary<string, Action<bool>>();
readonly Dictionary<string, Action<bool>> _propertyHandlersWithInit = new(StringComparer.Ordinal);

readonly Dictionary<string, Action> _propertyHandlers = new Dictionary<string, Action>();
readonly Dictionary<string, Action> _propertyHandlers = new(StringComparer.Ordinal);

readonly HashSet<string> _batchedProperties = new HashSet<string>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ internal static bool IsDefault(this IFontElement self)
return self.FontFamily == null && self.FontSize == Device.GetNamedSize(NamedSize.Default, typeof(Label), true) && self.FontAttributes == FontAttributes.None;
}

static Dictionary<string, FontFamily> FontFamilies = new Dictionary<string, FontFamily>();
static Dictionary<string, FontFamily> FontFamilies = new(StringComparer.Ordinal);


public static FontFamily ToFontFamily(this string fontFamily, string defaultFontResource = "FontFamilySemiBold")
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Build.Tasks/PerformanceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal class Statistic
public bool IsDetail;
}

readonly Dictionary<string, Statistic> _Statistics = new Dictionary<string, Statistic>();
readonly Dictionary<string, Statistic> _Statistics = new(StringComparer.Ordinal);

public Dictionary<string, Statistic> Statistics
{
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/AppLinkEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class AppLinkEntry : Element, IAppLinkEntry
/// <include file="../../docs/Microsoft.Maui.Controls/AppLinkEntry.xml" path="//Member[@MemberName='.ctor']/Docs/*" />
public AppLinkEntry()
{
keyValues = new Dictionary<string, string>();
keyValues = new(StringComparer.Ordinal);
}

/// <summary>Bindable property for <see cref="Title"/>.</summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ internal override void OnParentResourcesChanged(IEnumerable<KeyValuePair<string,
return;
}

var innerKeys = new HashSet<string>();
var innerKeys = new HashSet<string>(StringComparer.Ordinal);
var changedResources = new List<KeyValuePair<string, object>>();
foreach (KeyValuePair<string, object> c in Resources)
innerKeys.Add(c.Key);
Expand Down
3 changes: 2 additions & 1 deletion src/Controls/src/Core/DragAndDrop/DataPackagePropertySet.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;

Expand All @@ -12,7 +13,7 @@ public class DataPackagePropertySet : IEnumerable
/// <include file="../../../docs/Microsoft.Maui.Controls/DataPackagePropertySet.xml" path="//Member[@MemberName='.ctor']/Docs/*" />
public DataPackagePropertySet()
{
_propertyBag = new Dictionary<string, object>();
_propertyBag = new(StringComparer.Ordinal);
}

public object this[string key]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public partial class Application : IApplication
internal const string MauiWindowIdKey = "__MAUI_WINDOW_ID__";

readonly List<Window> _windows = new();
readonly Dictionary<string, WeakReference<Window>> _requestedWindows = new();
readonly Dictionary<string, WeakReference<Window>> _requestedWindows = new(StringComparer.Ordinal);
ILogger<Application>? _logger;

ILogger<Application>? Logger =>
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/Internals/NameScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class NameScope : INameScope
public static readonly BindableProperty NameScopeProperty =
BindableProperty.CreateAttached("NameScope", typeof(INameScope), typeof(NameScope), default(INameScope));

readonly Dictionary<string, object> _names = new Dictionary<string, object>();
readonly Dictionary<string, object> _names = new(StringComparer.Ordinal);
readonly Dictionary<object, string> _values = new Dictionary<object, string>();

object INameScope.FindByName(string name)
Expand Down
4 changes: 2 additions & 2 deletions src/Controls/src/Core/Registrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ static Registrar()
Registered = new Registrar<IRegisterable>();
}

internal static Dictionary<string, Type> Effects { get; } = new Dictionary<string, Type>();
internal static Dictionary<string, Type> Effects { get; } = new(StringComparer.Ordinal);

internal static Dictionary<string, IList<StylePropertyAttribute>> StyleProperties => LazyStyleProperties.Value;

Expand Down Expand Up @@ -319,7 +319,7 @@ public static void RegisterStylesheets(InitializationFlags flags)

static Dictionary<string, IList<StylePropertyAttribute>> LoadStyleSheets()
{
var properties = new Dictionary<string, IList<StylePropertyAttribute>>();
var properties = new Dictionary<string, IList<StylePropertyAttribute>>(StringComparer.Ordinal);
if (DisableCSS)
return properties;
var assembly = typeof(StylePropertyAttribute).Assembly;
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/ResourceDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class ResourceDictionary : IResourceDictionary, IDictionary<string, objec
{
const string GetResourcePathUriScheme = "maui://";
static ConditionalWeakTable<Type, ResourceDictionary> s_instances = new ConditionalWeakTable<Type, ResourceDictionary>();
readonly Dictionary<string, object> _innerDictionary = new Dictionary<string, object>();
readonly Dictionary<string, object> _innerDictionary = new(StringComparer.Ordinal);
ResourceDictionary _mergedInstance;
Uri _source;

Expand Down
4 changes: 2 additions & 2 deletions src/Controls/src/Core/ResourcesExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static IEnumerable<KeyValuePair<string, object>> GetMergedResources(this
var ve = element as IResourcesProvider;
if (ve != null && ve.IsResourcesCreated)
{
resources = resources ?? new Dictionary<string, object>();
resources = resources ?? new(StringComparer.Ordinal);
foreach (KeyValuePair<string, object> res in ve.Resources.MergedResources)
{
// If a MergedDictionary value is overridden for a DynamicResource,
Expand All @@ -33,7 +33,7 @@ public static IEnumerable<KeyValuePair<string, object>> GetMergedResources(this
var app = element as Application;
if (app != null && app.SystemResources != null)
{
resources = resources ?? new Dictionary<string, object>(8);
resources = resources ?? new Dictionary<string, object>(8, StringComparer.Ordinal);
foreach (KeyValuePair<string, object> res in app.SystemResources)
if (!resources.ContainsKey(res.Key))
resources.Add(res.Key, res.Value);
Expand Down
4 changes: 2 additions & 2 deletions src/Controls/src/Core/Routing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ namespace Microsoft.Maui.Controls
public static class Routing
{
static int s_routeCount = 0;
static Dictionary<string, RouteFactory> s_routes = new Dictionary<string, RouteFactory>();
static Dictionary<string, Page> s_implicitPageRoutes = new Dictionary<string, Page>();
static Dictionary<string, RouteFactory> s_routes = new(StringComparer.Ordinal);
static Dictionary<string, Page> s_implicitPageRoutes = new(StringComparer.Ordinal);
static HashSet<string> s_routeKeys;

const string ImplicitPrefix = "IMPL_";
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/Shell/ShellNavigationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ static Dictionary<string, string> ParseQueryString(string query)
{
if (query.StartsWith("?", StringComparison.Ordinal))
query = query.Substring(1);
Dictionary<string, string> lookupDict = new Dictionary<string, string>();
Dictionary<string, string> lookupDict = new(StringComparer.Ordinal);
if (query == null)
return lookupDict;
foreach (var part in query.Split('&'))
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/StyleSheets/Style.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ sealed class Style
{
}

public IDictionary<string, string> Declarations { get; set; } = new Dictionary<string, string>();
public IDictionary<string, string> Declarations { get; set; } = new Dictionary<string, string>(StringComparer.Ordinal);
Dictionary<KeyValuePair<string, string>, object> convertedValues = new Dictionary<KeyValuePair<string, string>, object>();

public static Style Parse(CssReader reader, char stopChar = '\0')
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1139,7 +1139,7 @@ internal override void OnParentResourcesChanged(IEnumerable<KeyValuePair<string,
return;
}

var innerKeys = new HashSet<string>();
var innerKeys = new HashSet<string>(StringComparer.Ordinal);
var changedResources = new List<KeyValuePair<string, object>>();
foreach (KeyValuePair<string, object> c in Resources)
innerKeys.Add(c.Key);
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/VisualStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public class VisualStateGroupList : IList<VisualStateGroup>

// Used to check for duplicate names; we keep it around because it's cheaper to create it once and clear it
// than to create one every time we need to validate
readonly HashSet<string> _names = new HashSet<string>();
readonly HashSet<string> _names = new HashSet<string>(StringComparer.Ordinal);

void Validate(IList<VisualStateGroup> groups)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Xaml/XamlServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ public object FindByName(string name)

public class XmlNamespaceResolver : IXmlNamespaceResolver
{
readonly Dictionary<string, string> namespaces = new Dictionary<string, string>();
readonly Dictionary<string, string> namespaces = new Dictionary<string, string>(StringComparer.Ordinal);

public IDictionary<string, string> GetNamespacesInScope(XmlNamespaceScope scope) => throw new NotImplementedException();

Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/CommandMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Microsoft.Maui
{
public abstract class CommandMapper : ICommandMapper
{
readonly Dictionary<string, Command> _mapper = new();
readonly Dictionary<string, Command> _mapper = new(StringComparer.Ordinal);

CommandMapper? _chained;

Expand Down
6 changes: 3 additions & 3 deletions src/Core/src/Fonts/FontRegistrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ namespace Microsoft.Maui
/// <inheritdoc cref="IFontRegistrar"/>
public partial class FontRegistrar : IFontRegistrar
{
readonly Dictionary<string, (string Filename, string? Alias, Assembly Assembly)> _embeddedFonts = new();
readonly Dictionary<string, (string Filename, string? Alias)> _nativeFonts = new();
readonly Dictionary<string, string?> _fontLookupCache = new();
readonly Dictionary<string, (string Filename, string? Alias, Assembly Assembly)> _embeddedFonts = new(StringComparer.Ordinal);
readonly Dictionary<string, (string Filename, string? Alias)> _nativeFonts = new(StringComparer.Ordinal);
readonly Dictionary<string, string?> _fontLookupCache = new(StringComparer.Ordinal);
readonly IServiceProvider? _serviceProvider;

IEmbeddedFontLoader _fontLoader;
Expand Down
4 changes: 2 additions & 2 deletions src/Core/src/HotReload/HotReloadHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ static void TransferState(IHotReloadableView oldView, IView newView)
}

static internal readonly WeakList<IHotReloadableView> ActiveViews = new WeakList<IHotReloadableView>();
static Dictionary<string, Type> replacedViews = new Dictionary<string, Type>();
static Dictionary<string, Type> replacedViews = new(StringComparer.Ordinal);
static Dictionary<IHotReloadableView, object[]> currentViews = new Dictionary<IHotReloadableView, object[]>();
static Dictionary<string, List<KeyValuePair<Type, Type>>> replacedHandlers = new Dictionary<string, List<KeyValuePair<Type, Type>>>();
static Dictionary<string, List<KeyValuePair<Type, Type>>> replacedHandlers = new(StringComparer.Ordinal);
public static void RegisterReplacedView(string oldViewType, Type newViewType)
{
if (!IsEnabled)
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/LifecycleEvents/LifecycleEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Microsoft.Maui.LifecycleEvents
{
public class LifecycleEventService : ILifecycleEventService, ILifecycleBuilder
{
readonly Dictionary<string, List<Delegate>> _mapper = new Dictionary<string, List<Delegate>>();
readonly Dictionary<string, List<Delegate>> _mapper = new(StringComparer.Ordinal);

public LifecycleEventService(IEnumerable<LifecycleEventRegistration> registrations)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/PropertyMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Microsoft.Maui
{
public abstract class PropertyMapper : IPropertyMapper
{
protected readonly Dictionary<string, Action<IElementHandler, IElement>> _mapper = new();
protected readonly Dictionary<string, Action<IElementHandler, IElement>> _mapper = new(StringComparer.Ordinal);

IPropertyMapper[]? _chained;

Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/WeakEventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.Maui
/// <include file="../docs/Microsoft.Maui/WeakEventManager.xml" path="Type[@FullName='Microsoft.Maui.WeakEventManager']/Docs/*" />
public class WeakEventManager
{
readonly Dictionary<string, List<Subscription>> _eventHandlers = new Dictionary<string, List<Subscription>>();
readonly Dictionary<string, List<Subscription>> _eventHandlers = new(StringComparer.Ordinal);

/// <include file="../docs/Microsoft.Maui/WeakEventManager.xml" path="//Member[@MemberName='AddEventHandler'][1]/Docs/*" />
public void AddEventHandler<TEventArgs>(EventHandler<TEventArgs> handler, [CallerMemberName] string eventName = "")
Expand Down
2 changes: 1 addition & 1 deletion src/Essentials/src/TextToSpeech/TextToSpeech.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public async Task SpeakAsync(string text, int max, SpeechOptions options, Cancel
for (var i = 0; i < parts.Count && !cancelToken.IsCancellationRequested; i++)
{
// We require the utterance id to be set if we want the completed listener to fire
var map = new Dictionary<string, string>
var map = new Dictionary<string, string>(StringComparer.Ordinal)
{
{ AndroidTextToSpeech.Engine.KeyParamUtteranceId, $"{guid}.{i}" }
};
Expand Down
2 changes: 1 addition & 1 deletion src/Essentials/src/Types/Shared/WebUtils.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ static class WebUtils
{
internal static IDictionary<string, string> ParseQueryString(string url)
{
var d = new Dictionary<string, string>();
var d = new Dictionary<string, string>(StringComparer.Ordinal);

if (string.IsNullOrWhiteSpace(url) || (url.IndexOf("?", StringComparison.Ordinal) == -1 && url.IndexOf("#", StringComparison.Ordinal) == -1))
return d;
Expand Down
4 changes: 2 additions & 2 deletions src/Essentials/src/VersionTracking/VersionTracking.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,15 @@ internal void InitVersionTracking()
IsFirstLaunchEver = !preferences.ContainsKey(versionsKey, sharedName) || !preferences.ContainsKey(buildsKey, sharedName);
if (IsFirstLaunchEver)
{
versionTrail = new Dictionary<string, List<string>>
versionTrail = new(StringComparer.Ordinal)
{
{ versionsKey, new List<string>() },
{ buildsKey, new List<string>() }
};
}
else
{
versionTrail = new Dictionary<string, List<string>>
versionTrail = new(StringComparer.Ordinal)
{
{ versionsKey, ReadHistory(versionsKey).ToList() },
{ buildsKey, ReadHistory(buildsKey).ToList() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public WebAuthenticatorResult(IDictionary<string, string> properties)
/// <summary>
/// The dictionary of key/value pairs parsed form the callback URI's query string.
/// </summary>
public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
public Dictionary<string, string> Properties { get; set; } = new(StringComparer.Ordinal);

/// <summary>Puts a key/value pair into the dictionary.</summary>
public void Put(string key, string value)
Expand Down
4 changes: 2 additions & 2 deletions src/Graphics/src/Graphics/Text/TextColors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Microsoft.Maui.Graphics.Text
{
public static class TextColors
{
public static Dictionary<string, string> StandardColors = new Dictionary<string, string>
public static Dictionary<string, string> StandardColors = new(StringComparer.OrdinalIgnoreCase)
{
{"BLACK", "#000000"},
{"NAVY", "#000080"},
Expand Down Expand Up @@ -158,7 +158,7 @@ public static float[] Parse(this string color)
//Remove # if present
if (!color.StartsWith("#", StringComparison.Ordinal))
{
if (!StandardColors.TryGetValue(color.ToUpperInvariant(), out color))
if (!StandardColors.TryGetValue(color, out color))
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Graphics/src/Text.Markdig/Renderer/SimpleCssParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static Dictionary<string, string> Parse(string css)
if (string.IsNullOrEmpty(css))
return null;

var values = new Dictionary<string, string>();
var values = new Dictionary<string, string>(StringComparer.Ordinal);

var entries = css.Split(';');
foreach (var entry in entries)
Expand Down
2 changes: 1 addition & 1 deletion src/SingleProject/Resizetizer/src/ResizetizeImages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public override System.Threading.Tasks.Task ExecuteAsync()

foreach (var img in resizedImages)
{
var attr = new Dictionary<string, string>();
var attr = new Dictionary<string, string>(StringComparer.Ordinal);
string itemSpec = Path.GetFullPath(img.Filename);

// Fix the item spec to be relative for mac
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

namespace Microsoft.Maui.Resizetizer
{
internal class TizenResourceXmlGenerator
{
static readonly IDictionary<string, string> resolutionMap = new Dictionary<string, string>
static readonly IDictionary<string, string> resolutionMap = new Dictionary<string, string>(StringComparer.Ordinal)
{
{ "LDPI", "from 0 to 240" },
{ "MDPI", "from 241 to 300" },
Expand Down

0 comments on commit 77de00e

Please sign in to comment.