Extenzní metoda - binární operace And pro enumerace
V diskuzním fóru se (po dlouhé době ) objevil jeden zajímavější dotaz, který se netýká ani toho, jak zobrazit druhý formulář v aplikaci, ba ani autor nebojuje s mizením dat po postbacku v ASP.NET aplikaci.
Ale vážně - autor dotazu by chtěl mít lepší syntaxi pro binární operaci And v enumeracích označených metaatributem Flags. Mně stávající C syntaxe (Rights & Rights.Add == Rights.Add) zcela vyhovuje a žádný další syntaktický cukřík hltat nechci, ale přesto mě zaujalo, jak by se dal problém, tedy spíš estetická preference náročného tazatele , řešit.
Tazatel přeposlal svůj dotaz i do konference na vývojáři, kde bylo nabídnuto řešení přes dočasné přetypování na typ Object a poté z typu Object na typ int. Jak zaznělo v kritice na vývojáři - "dirty" řešení je funkční, ale opakovaný boxing a unboxing hodnot není zrovna ta pravá vývojářská slast.
Zkusil jsem vymyslet jiné řešení - přiznám se, že IL jsem nezkoumal a žádné výkonnostní testy nedělal.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
namespace BinaryOpGenericTest
{
[Flags]
enum MyFlags
{
A = 1,
B = 2,
C = 4
}
static class EnumExtensions
{
private static Dictionary<Type, Delegate> m_operations = new Dictionary<Type, Delegate>();
public static bool Contains<T>(this T firstOperand, T secondOperand)
where T : struct
{
Type enumType = typeof(T);
if (!enumType.IsEnum)
{
throw new InvalidOperationException("Enum type parameter required");
}
Delegate funcImplementorBase = null;
m_operations.TryGetValue(enumType, out funcImplementorBase);
Func<T, T, bool> funcImplementor = funcImplementorBase as Func<T, T, bool>;
if (funcImplementor == null)
{
funcImplementor = buildFuncImplementor(secondOperand);
}
return funcImplementor(firstOperand, secondOperand);
}
private static Func<T, T, bool> buildFuncImplementor<T>(T val)
where T : struct
{
var first = Expression.Parameter(val.GetType(), "first");
var second = Expression.Parameter(val.GetType(), "second");
Expression convertSecondExpresion = Expression.Convert(second, typeof(int));
var andOperator = Expression.Lambda<Func<T, T, bool>>(Expression.Equal(
Expression.And(
Expression.Convert(first, typeof(int)),
convertSecondExpresion),
convertSecondExpresion),
new[] { first, second });
Func<T, T, bool> andOperatorFunc = andOperator.Compile();
m_operations[typeof(T)] = andOperatorFunc;
return andOperatorFunc;
}
}
class Program
{
static void Main(string[] args)
{
MyFlags flag = MyFlags.A | MyFlags.B;
Console.WriteLine(flag.Contains(MyFlags.A));
Console.WriteLine(EnumExtensions.Contains(flag, MyFlags.C));
Console.ReadLine();
}
}
}
Pár poznámek ke kódu.
- Nejdůležitější v kódu je generická extenzní metoda Contains. Nelze použít negenerickou metodu rozšiřující třídu Enum, protože poté začneme mít další problémy s přetypováváním hodnot z obecné Enum na konkrétní enumeraci. Řešením není ani negenerická metoda, v níž pracujeme pouze s třídou Enum. Enumerace jsou hodnotové typy, a proto je na generický parametr T aplikováno alespoň omezení struct. Jestliže bude zájem, mohu tato rozhodnutí, zde jen zběžně zmíněná, vysvětlit v nějakém dalším spotu.
- Když si nejsme jisti, že nám byla za generický typ předána enumerace, musíme provést v metodě Contains další kontrolu za běhu aplikace.
- Binární operace And je reprezentována delegáty. Delegáty dynamicky vytvářím za běhu aplikace pro každou enumeraci v metodě buildFuncImplementor. Delegát je vytvořen pomocí abstraktního syntaktického stromu (Expression tree) a poté je na výsledném výrazu (Expression) volána metoda Compile, která vrátí delegáta typu Func<T, T, bool>.
- Delegáty Func<T, T, bool> ukládáme do objektu Dictionary jako obecný typ Delegate - důvodem je to, že třída EnumExtensions není generická. Místo přetypovávání delegáta z předka Delegate na typ Func<T, T, bool> by bylo možné také použít pozdní vazbu voláním metody DynamicInvoke dostupné přímo ve třídě Delegate.
- Jestliže byste chtěli operaci And rozšířit i na další typy, které lze konvertovat na typ Int, mohla by se Vám hodit moje extenzní metoda pro detekci konverze generické proměnné na typ Int za běhu aplikace.
Sunday, 21 December 2008 12:22:20 (Central Europe Standard Time, UTC+01:00)
.NET Framework | Compact .Net Framework | LINQ
GSM Net Monitor verze 0.8.0.0. Alfa
Homepage aplikace.
Instalační cab.
Návod na rozchození lokalizace pozice pomocí BTS
Změny ve verzi 0.0.8.
- Odstraněna chyba při inicializaci pluginu. !Určitě aktualizujte!
- Lepší kompatibilita s Windows Mobile 6.1.
Důležité:
Před instalací nové verze vypněte v aplikaci sledování sítě.
Protože se jedná o AlFA preview, doporučuji před instalaci Net Monitoru mít v zařízení např. SPB Pocket Plus a v něm aktivovaný safe boot - jestliže by vám "vytuhlo" zařízení, nebudete muset dělat HR (Hard Reset), protože můžete při startu zařízení dočasně deaktivovat Today pluginy.
Update:Program pro jistotu ani nezkoušejte na zařízení, kde je nahráno TouchFlo, nebo jiný agresivní Today plugin. Riskujete zatuhnutí zařízení a je zbytečné mi potom psát dojemné maily, pokud nejste schopni si předtím udělat zálohu PDA nebo alespoň mít funkční safe-boot.
Po upgradu budete muset pravděpodobně znovu zadat svůj registrovaný email a přístupový kód. Pokud jste jej zapomněli, jděte na stránku http://gsmadmin.renestein.net a zadejte znovu svůj email. Aplikace vám nabídne opětovné zaslání emailu s přístupovým kódem.
Jestliže máte zařízení s VGA displejem, v pluginu jsou malé ikony a plugin je vykreslován na malé ploše. Plugin může být vykreslen na větší ploše - přes kontexové menu zobrazte Nastavení pluginu a změňte na záložce Základní nastavení preferovanou výšku na obrazovce Dnes. Ikony ale stejně zůstanou malé, proto chystám plnohotnotnou VGA verzi, do té doby lze plugin plně ovládat přes kontextové menu.
Thursday, 04 December 2008 16:19:46 (Central Europe Standard Time, UTC+01:00)
Net Monitor
Běh aplikace na Windows Mobile při vypnutém displeji a opětovné probuzení zařízení
Jedním z problémů, se kterými se vývojáři často potýkají, je, že PDA (přesněji 7Windows Mobile Professional a Classic zařízení - zařízení s dotykovým displejem) na rozdíl od Smartphonu (Windows Mobile Standard) po uplynutí doby nastavené v ovládacích panelech přecházejí do stavu "Off" (přesněji do stavu "Suspended" dle oficiální terminologie, stav Off má pouze Smartphone - my ale budeme dále ve spotu používat termín "off") , v němž je provádění kódu aplikace zcela "zmrazeno". Aplikace jsou "hibernovány" a můžeme je považovat za dočasné mrtvolky.
Občas potřebujeme, aby sice došlo k vypnutí displeje zařízení a my tak drasticky a navíc zbytečně neredukovali výdrž baterie, ale aby naše aplikace na pozadí stále běžela. To je první požadavek. Dalším z častých požadavků je schopnost aplikace probudit celé zařízení ze stavu "off".
Jestliže nám jde pouze o probuzení aplikace po uplynutí námi nastaveného času či při nějaké události (připojení k síti, změna proxy apod.), je řešení jednoduché - v našem arzenálu bude hrát prim omnipotentní funkce CeSetUserNotificationEx.
Když chceme v naší aplikaci pracovat i při vypnutém displeji (např. odpočítáváme sekundy v aplikaci "minutka") a současně probudit zařízení poté, co uběhla nastavená doba, musíme se začít přátelit s dalšími mocnými, avšak ke škodě samotné platformy Windows Mobile mizerně dokumentovanými API.
Co musíme zajistit:
- Zařízení nesmí přejít do "off" módu, ale musíme jej ponechat ve speciálním stavu, kdy je vypnutý displej, ale aplikace, které potřebují běžet, nejsou "zmrazeny". Námi požadovaný speciální stav se na Windows Mobile nazývá "unattended mód". Z hlediska uživatele je při "unattended módu" zařízení vypnuto, ale naše aplikace, která si o tento mód požádala, pokračuje ve své činnosti.
K tomu nám poslouží API PowerPolicyNotify.
PowerPolicyNotify(PowerMode.UnattendedMode, UNATTENDED_ON).
Funkci PowerPolicyNotify předáme v prvním argumentu požadavek na Unattended mód a ve druhém argumentu konstantu UNATTENDED_ON s hodnotou 1 - TRUE.
- V unattended módu chceme zařízení zcela probudit. Opět použijeme API PowerPolicyNotify.
PowerPolicyNotify(PowerMode.AppButtonPressed, 0)
Tentokrát ale předáme konstantu PowerMode.AppButtonPressed, která simuluje stisknutí tlačítka "On/Off" na zařízení. Druhý argument musí být 0, funkcí s ním při tomto volání nepracuje.
- Jestliže již nepotřebujeme běžet v "unattended módu", měli bychom dovolit zařízení, aby naši aplikaci hibernovalo. Nejpozději při ukončení aplikace se tedy musíme vzdát "unattended módu". Použijeme opět API PowerPolicyNotify s prvním argumentem PowerMode.UnattendedMode a ve druhém argumentu předáme konstantu UNATTENDED_OFF s hodnotou 0 - FALSE.
PowerPolicyNotify(PowerMode.UnattendedMode, UNATTENDED_OFF).
Sice jsem si ověřil, že při ukončení procesu je "unattended mód" zrušen samotnými Windows, ale je vždy příjemné pracovat s kódem, v němž nejsou obvyklé "prasácké" zlozvyky v managed kódu, a nenechat tedy v naší situaci uklízet jen Garbage Collector nebo správce procesů ve Windows.
V následujícím příkladu naleznete také tento zakomentovaný kód:
powerSettings = SetPowerRequirement("BKL1:", (int)DeviceState.FullOn, POWER_FORCE, IntPtr.Zero, 0);
if (powerSettings == IntPtr.Zero)
{
MessageBox.Show("Failed");
}
K zapnutí displeje je použito API SetPowerRequirement. Toto API ale v "unattended" módu selhává - vždy mi na zařízeních, která mám doma, vrací false. Dále musíte znát zkratku zařízení, pro které chcete nastavit nový režim. Na většině zařízení je displej identifikován zkratkou BKL1: - abyste si ale byli jisti, musíte název najít v registrech - klíče pod HKLM\Drivers\Active\*. API PowerPolicyNotify je tedy pro naše účely mnohem vhodnější.
Pozor: Některá zařízení (pokud se dobře pamatuji, tak např. MIO) nemají od výrobce podporu "unattended" módu a aplikace je stejně vždy zmrazena.
Zde je slíbený příklad - na formuláři je pouze listbox, do kterého je každých 5 vteřin přidáno další číslo. Můžete si ověřit, že čísla přibývají i v "unattended" módu. Po uplynutích každých 75 vteřin (konstanta WAKE_UP_COUNTER (15) * 5) je zařízení opakovaně probuzeno k "plnému vědomí", což se u něj projeví rozsvíceným displejem.
V ovládacích panelech (záložka Systém, položka Napájení, ikona Upřesnit) nastavte vypnutí displeje i zařízení po uplynutí jedné minuty neaktivity, abyste si příklad užili.
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
namespace PowerPDAProject
{
public delegate void MethodInvoker();
public partial class Form1 : Form
{
public const int UNATTENDED_ON = 1;
public const int POWER_FORCE = 0x1000;
public const int UNATTENDED_OFF = 0;
public const int WAKE_UP_COUNTER = 15;
private int m_secondsCount;
private IntPtr powerSettings;
public enum PowerMode
{
ReevaluateStat = 0x0001,
PowerChange = 0x0002,
UnattendedMode = 0x0003,
SuspendKeyOrPwrButtonPressed = 0x0004,
SuspendKeyReleased = 0x0005,
AppButtonPressed = 0x0006
}
[DllImport("CoreDll.dll")]
public static extern int PowerPolicyNotify(PowerMode powerMode, int flags);
public enum DeviceState : int
{
Unspecified = -1,
FullOn = 0,
LowOn,
StandBy,
Sleep,
Off,
Maximum
}
[DllImport("CoreDll.DLL")]
public static extern IntPtr SetPowerRequirement(String pvDevice, int DeviceState, int DeviceFlags, IntPtr pvSystemState, int StateFlags);
[DllImport("CoreDll.DLL")]
public static extern uint ReleasePowerRequirement(IntPtr hPowerReq);
public Form1()
{
InitializeComponent();
m_secondsCount = 0;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
bool result = (PowerPolicyNotify(PowerMode.UnattendedMode, UNATTENDED_ON) != 0);
MessageBox.Show(result.ToString());
timer1.Enabled = true;
}
private void timer1_Tick(object sender, EventArgs e)
{
m_secondsCount++;
listBox1.Invoke((MethodInvoker)(
() => listBox1.Items.Add(m_secondsCount.ToString())));
if (m_secondsCount % WAKE_UP_COUNTER == 0)
{
PowerPolicyNotify(PowerMode.AppButtonPressed, 0);
//powerSettings = SetPowerRequirement("BKL1:", (int)DeviceState.FullOn, POWER_FORCE, IntPtr.Zero, 0);
//if (powerSettings == IntPtr.Zero)
//{
// MessageBox.Show("Failed");
//}
}
}
private void Form1_Closing(object sender, CancelEventArgs e)
{
timer1.Enabled = false;
timer1.Dispose();
PowerPolicyNotify(PowerMode.UnattendedMode, UNATTENDED_OFF);
ReleasePowerRequirement(powerSettings);
}
}
}
Monday, 01 December 2008 18:23:53 (Central Europe Standard Time, UTC+01:00)
Compact .Net Framework | Mobilitky