Sunteți pe pagina 1din 31

1

Curs 3

Atribute. Interfete.
Atribute Ideea de baza: autodescrierea componentelor. Astfel compilatorul si CLR pot citi aceste informatii. De asemenea dezvoltatorul componentei are toate informatiile despre componenta intr-un singur loc. Atributele ne furnizeaza informatiile asociate cu tipul definit in C#. Important e ca aceasta informatie poate fi definita de dezvoltatorul tipului si nu este statica, legata de limbaj. Aceasta asociere de informatie urmeaza aceleasi principii folosite in dezvoltarea XML. Putem crea un atribut bazat pe orice informatie dorim. Exista un mecanism standard pentru definirea atributelor si pentru interogarile membrului sau tipului la run time. Toate atributele, preconstruite sau definite de utilizator, sunt derivate direct sau indirect din System.Attribute. Atributele mostenesc o anumita comportare implicita: atributul poate fi asociat cu orice element tinta; poate fi sau nu poate fi mostenit de un element derivat; instante multiple pot fi permise sau nu pe acelasi element tinta. Aceste comportari sunt specificate in AttributeUsageAttribute. Un atribut este o adnotare ce poate fi plasata pe un element al codului sursa si este folosit pentru a memora in momentul compilarii, informatii specifice aplicatiei. Aceasta informatie este memorata in metadata si poate fi accesata fie in timpul executiei aplicatiei, prin procesul cunoscut sub numele de reflexie sau cand un alt utilitar citeste metadata. Atributele pot schimba comportarea aplicatiei in momentul executiei. CLI predefineste anumite tipuri de atribute si le foloseste pentru a controla comportarea la runtime. Putem crea propriile noastre clase de atribute, mostenite din Attribute. Definitia unei asemenea clase include numele atributului, comportarea sa implicita si alte informatii. Exemplu Urmatorul exemplu creaza si atribuie atribute definite de utilizator unei anumite clase. Atributul contine numele programatorului si versiunea clasei.
using System; [AttributeUsage(AttributeTargets.Class| AttributeTargets.Struct, AllowMultiple=true)] public class Author : Attribute { string authorName; public double verSion; public Author(string name) { authorName = name; verSion = 1.0; }

2
public string getName() { return authorName; } } [Author("Some Author")] class FirstClass { /*...*/ } class SecondClass { /*...*/ } // no Author attribute

[Author("Some Author"), Author("Some Other Author", verSion=1.1)] class ThirdClass { /*...*/ } class AuthorInfo { public static void Main() { PrintAuthorInfo(typeof(FirstClass)); PrintAuthorInfo(typeof(SecondClass)); PrintAuthorInfo(typeof(ThirdClass)); } public static void PrintAuthorInfo(Type type) { Console.WriteLine("Author information for {0}", type); Attribute[] attributeArray = Attribute.GetCustomAttributes(type); foreach(Attribute attrib in attributeArray) { if (attrib is Author) { Author author = (Author)attrib; Console.WriteLine(" {0}, version {1:f}", author.getName(), author.verSion); } } Console.WriteLine(); } }

Iesirea este:
Author information for FirstClass Some Author, version 1.00 Author information for SecondClass Author information for ThirdClass Some Author, version 1.00 Some Other Author, version 1.10

3 Definirea atributelor Un atribut este in momentul de fata o clasa derivata din System.Attribute. Sintaxa folosita pentru a atasa un atribut la un tip sau membru este asemanatoare cu cea folosita la instantierea unei clase. Interogarea atributelor Pentru a interoga un tip sau un membru despre atributele atasate acestuia, trebuie sa utilizam reflexia (reflection). Reflection ne permite de a determina in mod dinamic in momentul executiei caracteristicile unei aplicatii. Putem folosi reflection pentru a citi metadata pentru un intreg assembly si a produce o lista a tuturor claselor, tipurilor si metodelor definite pentru acel assembly.

Atribute la nivel de clasa


Exemplu Presupunem ca dorim sa definim un atribut ce va defini calculatorul pe care va fi creat un obiect. Daca nu folosim atribute, atunci trebuie sa salvam aceasta informatie intr-o constanta sau un fisier de resurse al aplicatiei. Cu ajutorul atributelor, adnotam clasa cu calculatorul pe care vom crea obiectele astfel :
public enum RemoteServers { JEANVALJEAN, JAVERT, COSETTE } public class RemoteObjectAttribute : Attribute { public RemoteObjectAttribute(RemoteServers Server) { this.server = Server; } protected RemoteServers server; public string Server { get { return RemoteServers.GetName( typeof(RemoteServers), this.server); /* Daca folosim: return RemoteServers.GetName( typeof(RemoteServers), 1 ); rezultatul este JAVERT Vezi Enum in .NET FCL (Framework Class Library).

4
*/ } } } [RemoteObject(RemoteServers.COSETTE)] class MyRemotableClass { }

Pentru a determina serverul pe care se creaza obiectul putem folosi urmatorul cod (explicatiile sunt comentate in cod):
class Test { [STAThread] static void Main(string[] args) { // // Obtinem tipul obiectului asociat cu MyRemotableClass // Type type = typeof(MyRemotableClass); // // Dupa ce am obtinu obiectul, putem incepe interogarea lui // /* GetCustomAttributes returneaza un vector ale carui elemente sunt atributele obiectului. Parametrul true specifica daca cautarea se face si pentru membrii mosteniti. Din cauza ca nu avem mostenire in acest caz, putem folosi true/false cu acelasi efect. */ foreach (Attribute attr in type.GetCustomAttributes(true)) { /* Se incearca conversia atributului la RemoteObjectAttribute. Daca conversia nu reuseste atunci remoteAttr = null; */ RemoteObjectAttribute remoteAttr = attr as RemoteObjectAttribute; if (null != remoteAttr) { Console.WriteLine( "Create this object on {0}.", remoteAttr.Server); } } } }

Rezultatul este:
Create this object on COSETTE.

Pentru a defini mai multe atribute la o clasa vezi si exemplul din pagina web.

Atribute la nivel de metoda


Codul pentru interogarea atributelor unei metode este diferit de cel folosit la interogarea atributelor unei clase. Explicam acest lucru pe baza unui exemplu.
namespace AtributeMetode { public class AtributTranzactie : Attribute { public AtributTranzactie() { Console.WriteLine(Ctor AtributTranzactie); } } class Atx { [AtributTranzactie] public void Txt1() {} public void NonTxt() {} [AtributTranzactie] public void Txt2() {} } class Test { [STAThread] static void Main(string[] args) { // Clasa se califica cu numele spatiului // pentru ca am definit un namespace Type type = Type.GetType("AtributeMetode.Atx"); foreach (MethodInfo method in type.GetMethods()) { foreach (Attribute attr in method.GetCustomAttributes(true)) { if (attr is AtributTranzactie) { Console.WriteLine( "Metoda {0} are atributul AtributTranzactie.", method.Name); } } } } } }

Iesirea este: AtributTranzactie ctor

6 Metoda Txt1 are atributul AtributTranzactie. AtributTranzactie ctor Metoda Txt2 are atributul AtributTranzactie. Observatie: Cand atasam un atribut cu un constructor fara parametri, nu mai este nevoie de paranteze. Cateva explicatii: Type.GetMethods = regaseste un vector ce are elemente obiecte de tip MethodInfo. Fiecare element contine informatii despre o metoda din clasa Atx. MethodInfo.GetCustomAttributes = regaseste toate atributele create de utilizator pentru o metoda. Pentru a testa daca metoda are atributul AtributTranzactie folosim operatorul is. Exemplu 2 Se ataseaza atributul Serializable (atribut preconstruit) in vederea serializarii datelor unei clase. Vezi codul ce urmeaza (ex. din internet).
using System; using System.IO; using System.Runtime.Serialization.Formatters.Soap; [Serializable] public class User { public string userID; public string password; public string email; public string city; public void Save(string fileName) { FileStream s=new FileStream(fileName,FileMode.Create); SoapFormatter sf=new SoapFormatter(); sf.Serialize(s,this); } static void Main(string[] args) { User u=new User(); u.userID="firstName"; u.password="Zxfd12Qs"; u.email="asdf@qwer.com"; u.city="TheCity"; u.Save("user.txt"); } }

Atribute pentru campuri (field attributes)


Vom explica atributele pentru campuri pe baza unui exemplu, in care incercam sa validam lungimea unor siruri de caractere (user, password, email). user si password : cu lungimea intre 4 si 8 caractere ; email : cu lungimea intre 4 si 60 caractere ; Dorim validarea acestor campuri inainte de serializare. In caz ca cel putin un camp este invalid se abandoneaza operatia de serializare.

7 Definim un atribut ValidLength (clasa este ValidLengthAttribute), ce accepta doi parametri pozitionali, lungimea minima si lungimea maxima, si un parametru optional ce va contine mesajul afisat utilizatorului. Daca nu se specifica o valoare pentru parametrul optional se va afisa un mesaj implicit.
using using using using using System; System.IO; System.Runtime.Serialization.Formatters.Soap; System.Reflection; System.Collections;

[Serializable] public class User { [ValidLength(4,8,Message= "UserID should be between 4 and 8 characters long")] public string userID; [ValidLength(4,8,Message= "Password should be between 4 and 8 characters long")] public string password; [ValidLength(4,60)] public string email; public string city; public void Save(string fileName) { FileStream s=new FileStream(fileName,FileMode.Create); SoapFormatter sf=new SoapFormatter(); sf.Serialize(s,this); } static void Main(string[] args) { User u=new User(); u.userID="first"; u.password="Zxfd12Qs"; u.email=".com"; u.city=""; Validator v=new Validator(); if(!v.IsValid(u)) { foreach(string message in v.Messages) Console.WriteLine(message); } else {u.Save("user.txt");} } }

Pentru a valida obiectul, apelam metoda IsValid a obiectului Validator. Clasa Validator poate fi acum utilizata pentru a valida un obiect al oricarei clase apeland metosa IsValid.

Clasa pentru definirea atributului are urmatoarea descriere: [AttributeUsage(AttributeTargets.Property|AttributeTargets.Field)]


public class ValidLengthAttribute : Attribute { private int _min; private int _max;

8
private string _message; public ValidLengthAttribute(int min,int max) { _min=min; _max=max; } public string Message { get {return(_message);} set {_message=value;} } public string Min { get{return _min.ToString();} } public string Max { get{return _max.ToString();} } public bool IsValid(string theValue) { int length=theValue.Length; if(length >= _min && length <= _max) return true; return false; } }

iar clasa Validator arata astfel:


public class Validator { public ArrayList Messages=new ArrayList(); public bool IsValid(object anObject) { bool isValid=true; FieldInfo[] fields = anObject.GetType().GetFields(BindingFlags.Public|BindingFlags.Instance); foreach (FieldInfo field in fields) if(!isValidField(field,anObject)) isValid=false; return isValid; } private bool isValidField(FieldInfo aField,object anObject) { object[] attributes= aField.GetCustomAttributes(typeof(ValidLengthAttribute),true); if(attributes.GetLength(0) ==0) return true; return isValidField(aField,anObject, (ValidLengthAttribute)attributes[0]); } private bool isValidField(FieldInfo aField, object anObject,ValidLengthAttribute anAttr) { string theValue=(string)aField.GetValue(anObject); if (anAttr.IsValid(theValue)) return true; addMessages(aField,anAttr); return false; } private void addMessages(FieldInfo aField,ValidLengthAttribute anAttr) { if(anAttr.Message !=null) { Messages.Add(anAttr.Message); return;

9
} Messages.Add("Invalid range for "+ aField.Name+ ". Valid range is between " + anAttr.Min +" and " + anAttr.Max); }

Clasa Validator foloseste clasele reflection pentru a valida obiectul pasat ca parametru metodei IsValid. 1. Se extrag toate campurile publice din obiect folosind metoda GetType().GetFields(BindingFlags.Public|BindingFlags.Instance). Pentru fiecare camp, se extrage atributul definit de utilizator de tip ValidLengthAttribute folosind metoda GetCustomAttributes(typeof(ValidLengthAttribute),true). Daca nu se gaseste atributul nostru pentru un camp, metoda prepune campul ca fiind valid. Daca se gaseste atributul nostru pentru acel camp, se apeleaza metoda IsValid din ValidLengthAttribute pentru a valida valoarea campului.

Parametrii pozitionali si parametri cu nume


In exemplele anterioare, am examinat atasarea atributelor via constructorul lor. Vom examina cateva probleme legate de constructorul atributelor. In exemplul pentru atributele campurilor am folosit un atribut cu numele FieldAttribute. Constructorul acestuia arata astfel:
public FieldAttribute(Informatica an, String nume)

Folosind signatura constructorului, am atasat atributul la camp astfel:


[FieldAttribute(Informatica.AN_4, "Popescu")] public int nCuAtribut;

Putem folosi valori implicite folosind parametri pozitionali si parametri cu nume. Parametrii pozitionali sunt parametrii din constructorul atributului. Acestia trebuiesc specificati de fiecare data cand folosim atributul. Parametrii cu nume nu sunt definiti in constructorul atributului, acestia sunt campuri nestatice si proprietati (am folosit in primul exemplu din acest curs). Parametrii cu nume permit clientului sa seteze atributul campurilor si proprietatilor cand atributul este instantiat, fara a avea nevoie sa cream un constructor pentru fiecare combinatie posibila de campuri si proprietati. Fiecare ctor public poate defini un sir de parametri pozitionali. Sa vedem etapele pentru a folosi parametri cu nume.

10 Utilizatorul poate referentia ca parametru cu nume orice camp care nu este readonly, static sau const sau orice proprietate ce include un accesor set setter care nu este statica. Pentru a face FieldAttribute.Nume un parametru cu nume, il vom sterge din ctor pentru ca el exista deja ca o proprietate read/write: // public FieldAttribute(Informatica an, String nume) public FieldAttribut(string nume) { // this.an = an; this.nume = nume; Console.WriteLine("Ctor FieldAttribute"); } Utilizatorul poate atasa atributul in doua moduri:
[FieldAttribute("Popescu")] public int nCuAtribut; sau [FieldAttribute(Popescu,An = Informatica.AN_4)] public int nCuAtribut;

iar rezultatul va fi acelasi. O problema apare aici. Care este valoarea implicita pentru FieldAttribute.An? In caul de fata An este un enum si deci de tip int, ca atare compilatorul va initializa cu 0. Dar AN_1 este 0, deci am putea crede ca l-am initializat noi. Trucul de programare consta in a scrie enum astfel:
public enum Informatica { AN_1 = 1, AN_2, AN_3, AN_4, POSTUNIVERSITARE }

astfel ca putem identifica daca An a fost initializat de compilator sau de utilizator. In ctor va trebui sa adaugam cod pentru a seta An pe o valoare corecta scopurilor noastre. Acest cod poate fi:
public FieldAttribue(String nume) { if (this.An == 0) this.An = Informatica.AN_4; this.nume = nume; }

Vezi exemplul complet in pagina. Greseli cu parametrii cu nume Cand folosim parametri cu nume, trebuie sa specificam parametri pozitionali mai intai, si apoi parametri cu nume in orice ordine dorim.

11 Compilatorul incearca sa rezolve mai intai parametrii cu nume si apoi ce ramine, incearca sa gaseasca un ctor cu signatura potrivita. Parametrii cu nume pot fi orice camp sau proprietate accesibila publica incluzind o metoda setter care nu este statica sau constanta. Tipurile parametrului attribut Tipurile parametrilor pozitionali si ai celor cu nume pentru o clasa atribut sunt limitate la tipurile parametrului atributului, si care poate fi: bool, byte, char, double, float, int, long, short, string System.Type object tipul enum cu conditia ca oriunde se gaseste definit sa fie accesibil in mod public. un tablou unidimensional ce are ca tipuri de elemente unul din cele de mai sus. Atributul AttributeUsage AttributeUsage defineste modul cum atributele sunt folosite. Sintaxa este:
[AttributeUsage( validon, AllowMultiple = allowmultiple, Inherited = inherited )]

Observam un parametru pozitional si doi cu nume. Parametrul validon Parametrul validon este de tipul AttributeTarget, ne permite sa specificam tipurile pe care atributul poate fi atasat.
public enum AttributeTargets { Assembly = 0x0001, Module = 0x0002, Class = 0x0004, Struct = 0x0008, Enum = 0x0010, Constructor = 0x0020, Method = 0x0040, Property = 0x0080, Field = 0x0100, Event = 0x0200, Interface = 0x0400, Parameter = 0x0800, Delegate = 0x1000, All = Assembly Module Class Struct Enum Constructor Method Property Field Event Interface Parameter Delegate, ClassMembers Property = Class Struct Enum Field Event Delegate Constructor Interface, Method

12
}

Implicit este AttributeTarget.All. Membrii din enumerare pot fi folositi cu operatorul OR (|) ca in exemplul:
[AttributeUsage(AttributeTargets.Field AttributeTargets.Property)]

Folosim acest parametru cand vrem sa controlam exact modul de utilizare al unui atribut. In exemplelel anterioare am folosit atribute pentru clase, metode, campuri. Pentru a fi siguri ca atributele pe care le-am scris sunt folosite pentru tipurile pentru care au fost proiectate, putem indica in definitia atributului pentru ce este proiectat:
[AttributeUsage(AttributeTargets.Class)] public class RemoteObjectAttribute : Attribute {} [AttributeUsage(AttributeTargets.Method)] public class AtributTranzactie : Attribute {} [AttributeUsage(AttributeTargets.Field)] public class FieldAttribute : Attribute {} [RemoteObject(RemoteServers.COSETTE)] class SomeClass { [AtributTranzactie] public void Foo() {} [FieldAttribute("Bar", An = Informatica.AN_4)] public int Bar; }

Parametrul AllowMultiple Ne permite sa definim un atribut ce poate fi folosit o singura data pentru un camp sau de mai multe ori. Implicit toate atributele sunt folosite ca single, deci nu putem declara de doua ori acelasi atribut pentru un camp. Vezi exemplul:
public class SomethingAttribute : Attribute { public SomethingAttribute(String str){} } // Error: "Duplicate single-use Something attribute" [Something("abc")] [Something("def")] class MyClass{}

Parametru AllowMultiple rezolva aceasta problema. AllowMultiple = true atribut multiplicat; = false atribut single. Vezi si exemplul:

13

[AttributeUsage(AttributeTargets.All, AllowMultiple=true)] public class SomethingAttribute : Attribute { public SomethingAttribute(String str){} } [Something("abc")] [Something("def")] class MyClass{}

Parametrul Inherited Acest parametru ne spune daca atributul poate fi mostenit sau nu. Valoarea implicita este false. Parametrii AllowMultiple si Inherited trebuiesc considerati impreuna. Vezi tabelul de mai jos. Inherited AllowMultiple Result Atributul derivat suprascrie atributul true false de baza. Atributele derivate si cele din clasa de true true baza sunt combinate. De exemplu:
using System; using System.Reflection; namespace AttribInheritance { [AttributeUsage( AttributeTargets.All, // AllowMultiple=true, AllowMultiple=false, Inherited=true )] public class SomethingAttribute : Attribute { private string name; public string Name { get { return name; } set { name = value; } } public SomethingAttribute(string str) { this.name = str; } } [Something("abc")] class MyClass { }

14
[Something("def")] class Another : MyClass { } class Test { [STAThread] static void Main(string[] args) { Type type = Type.GetType("AttribInheritance.Another"); foreach (Attribute attr in type.GetCustomAttributes(true)) type.GetCustomAttributes(false)) { SomethingAttribute sa = attr as SomethingAttribute; if (null != sa) { Console.WriteLine( "Custom Attribute: {0}", sa.Name); } } } } }

//

Cu AllowMultiple = false, rezultatul este:


Custom Attribute: def

iar cu AllowMultiple = true, rezultatul devine:


Custom Attribute: def Custom Attribute: abc

Identificatorii atributului Trebuie sa precizam la ce se aplica un atribut: la o metoda sau la valoarea returnata de metoda. Din acest punct de vedere urmatorul cod este ambiguu.
class SomeClass { [HRESULT] public long Foo() { return 0; } }

In COM, HRESULT reprezinta valoarea returnata de metodele din interfata (mai putin AddRef si Release, acestea returneaza ULONG). Putem defini atributul HRESULT astfel:
public class HRESULTAttribute : Attribute { public HRESULTAttribute(){} }

15

Alte ambiguitati sunt date de urmatoarele situatii:


Metode vs. tip returnat; Evenimente vs. camp vs. proprietate ; Delegate vs. tip returnat; Proprietate vs. accessor vs. valoare returnata din getter vs. valoarea parametrului unei metode set;

Pentru a fixa lucrurile, putem folosi identificatorul atributului, listat mai jos:

assembly module type method property event field param return

Sintaxa este:
identificator:atribut

Atentie la : Vezi si exemplul de mai jos.


class SomeClass { [HRESULT] public long Foo() { return 0; } [return: HRESULT] public long Bar() { return 0; } }

Alte exemple:
class SomeClass { [method: HRESULT] public long Foo() { return 0; } [return: HRESULT] public long Bar() { return 0; } [property: HRESULT] public long Goo { get { return 12345; } } }

Vom folosi tehnica reflexiei pentru a determina identificatorul atributului, ca in exemplul urmator:

16
static void Main(string[] args) { Type type = Type.GetType("AttribIdentifiers.SomeClass"); foreach (MethodInfo m in type.GetMethods()) { foreach (Attribute a in m.GetCustomAttributes(true)) { if (a is HRESULTAttribute) { Console.WriteLine( "method: {0}, " + "CustomAttributes: {1}", m.Name, a); } } ICustomAttributeProvider icap = m.ReturnTypeCustomAttributes; foreach (Attribute a in icap.GetCustomAttributes(true)) { Console.WriteLine( "method: {0}, " + "ReturnTypeCustomAttribs: {1}", m.Name, a); } } foreach (MemberInfo m in type.GetProperties()) { foreach (Attribute a in m.GetCustomAttributes(true)) { Console.WriteLine( "property: {0}, " + "CustomAttributes: {1}", m.Name, a); } } }

Rezultatul este:
method: Foo, CustomAttributes: AttribIdentifiers.HRESULTAttribute method: Bar, ReturnTypeCustomAttribs: AttribIdentifiers.HRESULTAttribute property: Goo, CustomAttributes: AttribIdentifiers.HRESULTAttribute

Atribute predefinite Cadrul de lucru .NET ofera clase de atribute predefinite, dintre care mai importante sunt: Predefined .NET Valid Targets Attribute AttributeUsage Class CLSCompliant All Description Specifies the valid usage of another attribute class. Indicates whether a program element is compliant with the Common Language Specification (CLS).

17 Predefined .NET Valid Targets Attribute Conditional DllImport MTAThread NonSerialized Obsolete ParamArray Serializable STAThread StructLayout ThreadStatic Method Method Method (Main) Field Description Indicates that the compiler can ignore any calls to this method if the associated string is defined. Specifies the DLL location that contains the implementation of an external method. Indicates that the default threading model for an application is multithreaded apartment (MTA). Applies to fields of a class flagged as Serializable; specifies that these fields wont be serialized.

All except Marks an element obsoletein other words, it Assembly, Module, informs the user that the element will be removed Parameter, and in future versions of the product. Return Allows a single parameter to be implicitly treated Parameter as a params (array) parameter. Class, struct, enum, Specifies that all public and private fields of this delegate type can be serialized. Indicates that the default threading model for an Method (Main) application is STA. Specifies the nature of the data layout of a class or Class, struct struct, such as Auto, Explicit, or Sequential. Implements thread-local storage (TLS)in other words, the given static field isnt shared across Field (static) multiple threads and each thread has its own copy of the static field.

Atributul Conditional Ideea: Apelul unei metode poate fi ignorat daca nu este definita o anumita valoare. Exemplu:
[Conditional("DEBUG")] public void SomeDebugFunc() { Console.WriteLine("SomeDebugFunc"); }

iar cod complet:


using System; using System.Diagnostics; namespace CondAttrib { class Thing { private string name; public Thing(string name) { this.name = name; SomeDebugFunc(); SomeFunc(); }

18
public void SomeFunc() { Console.WriteLine("SomeFunc"); } [Conditional("DEBUG")] public void SomeDebugFunc() { Console.WriteLine("SomeDebugFunc"); } } public class Class1 { [STAThread] static void Main(string[] args) { Thing t = new Thing("T1"); } } }

Iesirea este:
SomeDebugFunc SomeFunc

Pentru a vedea efectele acestui cod trebuiesc modificate proprietatile proiectului. Compilatorul decide daca functia este compilata sau nu. Putem combina atributul Conditional cu directivele de preprocesare ca in exemplul:
#if DEBUG SomeDebugFunc(); #else SomeFunc(); #endif Conditional este un atribut cu AllowMultiple = true.

Atributele DllImport si StructLayout Codul C# poate apela functii in cod nativ ce se gaseste in DLL. Aceasta caracteristica a runtime-ului se numeste platform invoke. Exemplu Vom apela functia Win32 API MessageBoxA ce se gaseste in user32.dll. Codul este:
public class Test { [DllImport ("user32.dll")] public static extern int MessageBoxA ( int h, string m, string c, int type); [STAThread] public static void Main(string[] args) { MessageBoxA(0, "Hello World", "nativeDLL", 0); } }

19

Runtime .NET transmite parametrii din codul C# manged catre codul nativ din DLL. Sintaxa completa pentru DllImport este:
[DllImport("user32", EntryPoint="MessageBoxA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern int MessageBoxA ( int h, string m, string c, int type);

Pentru a transmite structuri din codul managed catre codul unmanaged si invers trebuie sa folosim atributul StructLayout, ce are ca efect construierea structurii exact ca in declaratia ei initiala (adica secvential). Este obligatoriu acest lucru. Exemplu pentru apelul functiei GetLocalTime().
[StructLayout(LayoutKind.Sequential)] public class SystemTime { public ushort wYear; public ushort wMonth; public ushort wDayOfWeek; public ushort wDay; public ushort wHour; public ushort wMinute; public ushort wSecond; public ushort wMilliseconds; }

In continuare putem apela functia astfel:


public static void Main(string[] args) { SystemTime st = new SystemTime(); GetLocalTime(st); string s = String.Format("date: {0}-{1}-{2}", st.wMonth, st.wDay, st.wYear); string t = String.Format("time: {0}:{1}:{2}", st.wHour, st.wMinute, st.wSecond); string u = s + ", " + t; MessageBoxA(0, u, "Now", 0); }

20

Interfete
Interfetele ne dau posibilitatea de a defini o multime de metode si proprietati pe care clasa selectata le poate implementa. Din punct de vedere conceptual interfetele sunt contracte intre doua parti de cod separate. Lucrurile stau in felul urmator: se defineste o interfata (contine metode abstracte); interfata defineste o anumita comportare ; se defineste o clasa ce implementeaza aceasta interfata, se spune ca clasa este derivata din interfata ; clientii vor utiliza clasa ce a implementat interfata. Utilizarea interfetei In C#, o interfata este un concept de prima clasa, adica o trasatura preconstruita a limbajului, ce declara un tip referinta ce include numai declaratiile metodelor. Declararea interfetelor Interfetele pot contine metode, proprietati, indexeri si evenimente, si nici unul nu este implementat de interfata insasi. Interfetele sunt definte ca o clasa abstracta. Exemple
interface IValidate { bool Validate(); }

Observatii

Nu trebuie sa declaram ca metodele sunt pur virtuale (=0).


interface IExampleInterface { // Example property declaration int testProperty { get; } // Example event declaration event testEvent Changed; // Example indexer declaration string this[int index] { get; set; } }

Implementarea interfetelor Fiecare clasa ce implementeaza o interfata trebuie sa defineasca fiecare membru al interfetei. Sa examinam urmatorul exemplu.

21
using System; public class Control { public Control(string data) { Data = data; } protected string data; public string Data { get { return data; } set { data = value; } } } interface IValidate { bool Validate(); } class SSN : Control, IValidate // implementeaza interfata IValidate { const char DELIMITER = '-'; public SSN(string val) : base(val) {} public bool Validate() { Console.WriteLine("[SSN.Validate] : Validating '{0}'", data); return (11 == data.Length) && (CorrectFormat()); } protected bool CorrectFormat() { bool correctFormat = true; for (int i = 0; (correctFormat && i < data.Length); i++) { correctFormat = ((IsDelimiterPosition(i) && data[i] == DELIMITER) (IsNumberPosition(i) && char.IsNumber(data[i]))); } return correctFormat; } protected bool IsDelimiterPosition(int i) { return (i == 3 i == 6); } protected bool IsNumberPosition(int i) { return (i != 3 && i != 6); } } class InterfacesApp { public static void Main(string[] args) { string data = "";

22
if (0 < args.GetLength(0)) data = args[0]; SSN ssn = new SSN(data); // ... // Pentru a valida controlul, scriem urmatorul cod // o conversie a obiectului... IValidate val = (IValidate)ssn; Console.WriteLine("[Main] Calling SSN.Validate"); // // apelam metoda din interfata // bool success = val.Validate(); Console.WriteLine("[Main] The validation of " + "SSN '{0}' was {1}successful", ssn.Data, (true == success ? "" : "NOT ")); } }

Operatorii is si as Operatorul is test existenta interfata Pentru a testa un obiect daca implementeaza o anumita interfata folosim operatorul is. Operatorul is ne permite sa controlam in momentul executiei daca un tip este compatibil cu alt tip. Sintaxa este:
expresie is ttip

Vezi exemplul ce urmeaza.


using System; using System.Collections; public class Control { public Control(string data) { Data = data; } protected string data; public string Data { get { return data; } set { data = value; } } } interface IValidate { bool Validate(); }

23

class SSN : Control , IValidate { const char DELIMITER = '-'; public SSN(string val) : base(val) {} public bool Validate() { Console.WriteLine("[SSN.Validate] : Validating '{0}'", data); return (11 == data.Length) && (CorrectFormat()); } protected bool CorrectFormat() { bool correctFormat = true; for (int i = 0; (correctFormat && i < data.Length); i++) { correctFormat = ((IsDelimiterPosition(i) && data[i] == DELIMITER) (IsNumberPosition(i) && char.IsNumber(data[i]))); } return correctFormat; } protected bool IsDelimiterPosition(int i) { return (i == 3 i == 6); } protected bool IsNumberPosition(int i) { return (i != 3 && i != 6); } } class Address : Control { public Address(string data) : base(data) {} } class IsOperatorApp { public static void Main() { ArrayList controls = new ArrayList(); Console.WriteLine("Adding an SSN to the " + "controls array"); SSN ssn = new SSN("555-55-5555"); controls.Add(ssn); Console.WriteLine("Adding an Address to the " + "controls array"); Address addr = new Address("1 Microsoft Way"); controls.Add(addr); Console.WriteLine("\nIterating through array..."); foreach (Control control in controls) {

24
if (control is IValidate) { Console.WriteLine("\n\t{0} implements IValidate", control); Console.Write("\t"); bool b = ((IValidate)control).Validate(); Console.WriteLine("\tValidation {0}", (b == true ? "succeded" : "failed")); } else { Console.WriteLine("\t{0} does NOT implement " + "IValidate", control); } } } }

Operatorul as Operatorul as face conversia intre tipurile compatibile si are urmatoarea sintaxa: obiect = expresie as tip unde expresie este un tip referinta. In urma acestei conversii trebuie scris cod de verificare a conversiei, adica daca obiect are o valoare valida. Exemplu
using System; using System.Collections; public class Control { public Control(string data) { Data = data; } protected string data; public string Data { get { return data; } set { data = value; } } } interface IValidate { bool Validate(); } class SSN : Control , IValidate { const char DELIMITER = '-'; public SSN(string val) : base(val) {}

25
public bool Validate() { Console.WriteLine("[SSN.Validate] : Validating '{0}'", data); return (11 == data.Length) && (CorrectFormat()); } protected bool CorrectFormat() { bool correctFormat = true; for (int i = 0; (correctFormat && i < data.Length); i++) { correctFormat = ((IsDelimiterPosition(i) && data[i] == DELIMITER) (IsNumberPosition(i) && char.IsNumber(data[i]))); } return correctFormat; } protected bool IsDelimiterPosition(int i) { return (i == 3 i == 6); } protected bool IsNumberPosition(int i) { return (i != 3 && i != 6); } } class Address : Control { public Address(string data) : base(data) {} } class AsOperatorApp { public static void Main() { ArrayList controls = new ArrayList(); Console.WriteLine("Adding an SSN to the " + "controls array"); SSN ssn = new SSN("555-55-5555"); controls.Add(ssn); Console.WriteLine("Adding an Address to the " + "controls array"); Address addr = new Address("1 Microsoft Way"); controls.Add(addr); Console.WriteLine("\nIterating through array..."); IValidate iValidate; foreach (Control control in controls) { iValidate = control as IValidate; if (null != iValidate) { Console.WriteLine("\n\t{0} implements IValidate", control); Console.Write("\t");

26
bool b = iValidate.Validate(); Console.WriteLine("\tValidation {0}", (b == true ? "succeded" : "failed")); } else { Console.WriteLine("\n\t{0} does NOT " + "implement IValidate", control); } } } }

Ascunderea numelor din interfete. Rezolvarea coliziunilor de nume. O metoda de apel al unei interfete este:
ClasaEx clx = new ClasaEx(); IInterfataEx iex = (IInterfataEx)clx; iex.Metoda(); // metoda din interfata

Observatie : Putem apela si prin sintaxa :


clx.Metoda() ;

Avem posibilitatea de a ascunde membrii publici ai unei interfete, in special a celor ce nu ne sunt de folos. Pentru a ascunde un membru al interfetei este nevoie sa stergem modificatorul de acces public si sa calificam numele membrului cu numele interfetei :
using System; public interface IDataBound { void Bind(); } public class EditBox : IDataBound { // IDataBound implementation void IDataBound.Bind() { Console.WriteLine("Binding to data store..."); } } class NameHiding2App { public static void Main() { Console.WriteLine(); EditBox edit = new EditBox(); Console.WriteLine("Calling EditBox.Bind()...");

27
// ERROR: This line won't compile because // the Bind method no longer exists in the // EditBox class's namespace. edit.Bind(); Console.WriteLine(); IDataBound bound = (IDataBound)edit; Console.WriteLine("Calling (IDataBound)" + "EditBox.Bind()..."); // This is OK because the object was cast to // IDataBound first. bound.Bind(); } }

Cand ascundem un membru, nu putem folosi modificatori de acces. Coliziuni de nume Consideram urmatorul exemplu in care avem doua interfete ce contin o metoda cu acelasi nume, SaveData, dar cu functionalitati diferite in cadrul fiecarei interfete.
using System; interface ISerializable { void SaveData(); } interface IDataStore { void SaveData(); } class Test : ISerializable, IDataStore { public void SaveData() { Console.WriteLine("Test.SaveData called"); } } class NameCollisions1App { public static void Main() { Test test = new Test(); Console.WriteLine("Calling Test.SaveData()"); test.SaveData(); // aici apare ambiguitatea } }

28

Urmatorul cod este si mai ambiguu:


using System; interface ISerializable { void SaveData(); } interface IDataStore { void SaveData(); } class Test : ISerializable, IDataStore { public void SaveData() { Console.WriteLine("Test.SaveData called"); } } class NameCollisions2App { public static void Main() { Test test = new Test(); Console.WriteLine("Testing to see if Test " + "implements ISerializable..."); Console.WriteLine("ISerializable is {0}implemented\n", test is ISerializable ? "" : "NOT "); Console.WriteLine("Testing to see if Test " + "implements IDataStore..."); Console.WriteLine("IDataStore is {0}implemented\n", test is IDataStore ? "" : "NOT "); } }

Clasa Test apare ca implementind ambele interfete. Codul se executa (atentie si la versiunea de C#). Aici trebuie sa fim atenti si la mesajele de averisment emise de compilator :
NameCollisions2.cs(29,21): warning CS0183: The given expression is always of the provided ('ISerializable') type NameCollisions2.cs(33,21): warning CS0183: The given expression is always of the provided ('IDataStore') type

Rezolvare problema
using System; interface ISerializable { void SaveData(); }

29
interface IDataStore { void SaveData(); } class Test : ISerializable, IDataStore { void IDataStore.SaveData() { Console.WriteLine("[Test.SaveData] IDataStore " + "implementation called"); } void ISerializable.SaveData() { Console.WriteLine("[Test.SaveData] ISerializable " + "implementation called"); } } class NameCollisions3App { public static void Main() { Test test = new Test(); Console.WriteLine("[Main] " + "Testing to see if Test implements " + "ISerializable..."); Console.WriteLine("[Main] " + "ISerializable is {0}implemented", test is ISerializable ? "" : "NOT "); ((ISerializable)test).SaveData(); Console.WriteLine(); Console.WriteLine("[Main] " + "Testing to see if Test implements "IDataStore..."); Console.WriteLine("[Main] " + "IDataStore is {0}implemented", test is IDataStore ? "" : "NOT "); ((IDataStore)test).SaveData(); } } " +

Interfete si mostenirea

Clasa de baza ce implementeaza interfata contine o metoda ce are nume identic cu al unei metode din interfata

Exemplificare
using System; public class Control { public void Serialize() {

30
Console.WriteLine("Control.Serialize called"); } } public interface IDataBound { // EditBox never implements this, but it still compiles!!! void Serialize(); } public class EditBox : Control, IDataBound { } class InterfaceInh1App { public static void Main() { EditBox edit = new EditBox(); edit.Serialize(); } }

Codul se compileaza desi nu am scris cod pentru Serialize din IDataBound. Daca codul executa corect ceea ce am vrut noi este o alta problema. In fapt se apelaeaza Serialize din clasa de baza. Folosirea operatorului is nu rezolva problema neimplementarii unei metode din interfata. Practic is spune ca o anumita clasa suporta o anumita interfata, dar nu ca o si implementeaza.
Vezi exemplu: using System; public class Control { public void Serialize() { Console.WriteLine("Control.Serialize called"); } } public interface IDataBound { void Serialize(); } public class EditBox : Control, IDataBound {} class InterfaceInh2App { public static void Main() { EditBox edit = new EditBox(); IDataBound bound = edit as IDataBound; if (bound != null) { Console.WriteLine("IDataBound is supported..."); bound.Serialize(); }

31
else { Console.WriteLine("IDataBound is NOT supported..."); } } }

2. Clasa derivata din calas ce implementeaza o interfata are o metoda cu acelasi nume ca cea din clasa de baza, nume ce corespunde unei metode din interfata . Sa analizam urmatorul exemplu:
using System; interface ITest { void Foo(); } // Base implements ITest. class Base : ITest { public void Foo() { Console.WriteLine("Base.Foo (ITest implementation)"); } } class MyDerived : Base { public new void Foo() { Console.WriteLine("MyDerived.Foo"); } } public class InterfaceInh3App { public static void Main() { Console.WriteLine("InterfaceInh3App.Main : " + "Instantiating a MyDerived class"); MyDerived myDerived = new MyDerived(); Console.WriteLine(); Console.WriteLine("InterfaceInh3App.Main : " + "Calling MyDerived.Foo - Which method will be " + "called?"); myDerived.Foo(); Console.WriteLine(); Console.WriteLine("InterfaceInh3App.Main : " + "Calling MyDerived.Foo - Casting to ITest " + "interface..."); ((ITest)myDerived).Foo(); } }

new si casting-ul din ultima linie de cod rezolva problema in mod corect. Se recomanda folosirea cast-ului in utilizarea metodelor din interfete.

S-ar putea să vă placă și