Sunteți pe pagina 1din 32

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

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;

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

public string getName()

{

return authorName;

}

}

[Author("Some Author")] class FirstClass

{

 

/*

*/

}

21.01.2007

2

class SecondClass // nu are atributul Author

{

 

/*

*/

}

[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

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

Definirea atributelor

21.01.2007

3

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).

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

}

}

*/

}

21.01.2007

[RemoteObject(RemoteServers.COSETTE)] class MyRemotableClass

{

}

4

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 obtinut 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.

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

Atribute la nivel de metoda

21.01.2007

5

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

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

Metoda Txt1 are atributul AtributTranzactie. AtributTranzactie ctor Metoda Txt2 are atributul AtributTranzactie.

21.01.2007

6

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, valabil pentru .NET 2.0).

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.

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

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.

21.01.2007

7

using System; using System.IO; using System.Runtime.Serialization.Formatters.Soap; using System.Reflection; using 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; private string _message;

public ValidLengthAttribute(int min,int max)

{

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

8

_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;

}

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

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

9

}

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 presupune 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.

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.

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

Pentru a face FieldAttribute.Nume un parametru cu nume, il vom sterge din ctor pentru ca el exista deja ca o proprietate read/write:

10

21.01.2007

// 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. Compilatorul incearca sa rezolve mai intai parametrii cu nume si apoi ce ramine, incearca sa gaseasca un ctor cu signatura potrivita.

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

Parametrii cu nume pot fi orice camp sau proprietate accesibila publica – incluzind o metoda setter – care nu este statica sau constanta.

11

21.01.2007

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

=

Class │ Struct │ Enum │ Constructor │ Method │

Property │ Field │ Event │ Delegate │ Interface,

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

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:

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

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

true

false

Atributul derivat suprascrie atributul de baza.

true

true

Atributele derivate si cele din clasa de 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

{

}

[Something("def")]

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

class Another : MyClass

21.01.2007

14

 

{

}

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(){}

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

Alte ambiguitati sunt date de urmatoarele situatii:

21.01.2007

15

1. Metode vs. tip returnat;

2. Evenimente vs. camp vs. proprietate ;

3. Delegate vs. tip returnat;

4. 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:

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

static void Main(string[] args)

21.01.2007

16

{

 

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 Attribute

Valid Targets

Description

AttributeUsage

Class

Specifies the valid usage of another attribute class.

CLSCompliant

All

Indicates whether a program element is compliant with the Common Language Specification (CLS).

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

17

Predefined .NET Attribute

Valid Targets

Description

Conditional

Method

Indicates that the compiler can ignore any calls to this method if the associated string is defined.

DllImport

Method

Specifies the DLL location that contains the implementation of an external method.

MTAThread

Method (Main)

Indicates that the default threading model for an application is multithreaded apartment (MTA).

NonSerialized

Field

Applies to fields of a class flagged as Serializable; specifies that these fields won’t be serialized.

Obsolete

All except Assembly, Module, Parameter, and Return

Marks an element obsolete—in other words, it informs the user that the element will be removed in future versions of the product.

ParamArray

Parameter

Allows a single parameter to be implicitly treated as a params (array) parameter.

Serializable

Class, struct, enum, delegate

Specifies that all public and private fields of this type can be serialized.

STAThread

Method (Main)

Indicates that the default threading model for an application is STA.

StructLayout

Class, struct

Specifies the nature of the data layout of a class or struct, such as Auto, Explicit, or Sequential.

ThreadStatic

Field (static)

Implements thread-local storage (TLS)—in other words, the given static field isn’t shared across 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();

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

public void SomeFunc()

{ Console.WriteLine("SomeFunc"); }

[Conditional("DEBUG")] public void SomeDebugFunc()

21.01.2007

{ Console.WriteLine("SomeDebugFunc"); }

}

public class Class1

{

[STAThread] static void Main(string[] args)

{

Thing t = new Thing("T1");

}

}

}

Iesirea este:

SomeDebugFunc

SomeFunc

18

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);

}

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

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 construirea 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);

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

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:

20

21.01.2007

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 IExempluInterface

{

 

// Exemplu de declarare a unei proprietati int testProperty { get; }

// Exemplu de declarare a unui eveniment event testEvent Changed;

// Exemplu de declarare a unui indexer 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.

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

using System;

public class Control

{

21.01.2007

21

public Control(string data) { Data = data; }

protected string data; public string Data

{

get { return data; } set { data = value; }

}

}

interface IValidate

{

bool Validate();

}

// implementeaza interfata IValidate 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); }

}

// Test pentru tipul construit // Folosit din linia de comanda

class InterfacesApp

{

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

public static void Main(string[] args)

22

{

 

string data = "";

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] Apel 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; }

}

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

interface IValidate

{

bool Validate();

}

21.01.2007

23

// Clasa va implementa interfata IValidate

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); }

}

// Aceasta clasa nu este derivata din IValidate

class Address : Control

{

public Address(string data) : base(data) {}

}

// Test operator is

class IsOperatorApp

{

public static void Main()

{

// 1. Vom pune in acest ArrayList obiecte instantiate din SSN // si Address. // 2. Vom itera acest vector si pentru obiectele instantiate // din SSN vom apela metoda Validate.

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

ArrayList controls = new ArrayList();

24

Console.WriteLine("Adauga un obiect SSN in tabloul controls "); SSN ssn = new SSN("555-55-5555"); controls.Add(ssn);

Console.WriteLine("Adauga un obiect Address in tabloul controls”); Address addr = new Address("Strada Sperantei"); controls.Add(addr);

Console.WriteLine("\nParcurgerea elementelor tabloului foreach (Control control in controls)

”);

{

 

if (control is IValidate)

{

Console.WriteLine("\n\t{0} implementeaza IValidate", control); Console.Write("\t"); bool b = ((IValidate)control).Validate(); Console.WriteLine("\tValidare {0}", (b == true ? "succeded" : "failed"));

}

else

{

Console.WriteLine("\t{0} nu implementeaza " + "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; }

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

}

interface IValidate

{

bool Validate();

}

21.01.2007

class SSN : Control , IValidate

25

{

 

const char DELIMITER = '-';

public SSN(string val) : base(val) {}

public bool Validate()

{

 

Console.WriteLine("[SSN.Validate] : Validare '{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("Adauga un obiect SSN in vectorul control"); SSN ssn = new SSN("555-55-5555"); controls.Add(ssn);

Console.WriteLine("Adauga un obiect Address in vectorul controls"); Address addr = new Address("Strada Sperantei"); controls.Add(addr);

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

Console.WriteLine("\nIterare elemente din vector IValidate iValidate; foreach (Control control in controls)

");

26

{

 

iValidate = control as IValidate;

if (null != iValidate)

{

Console.WriteLine("\n\t{0} implementeaza IValidate", control); Console.Write("\t"); bool b = iValidate.Validate(); Console.WriteLine("\tValidare {0}", (b == true ? "succeded" : "failed"));

}

else

{

Console.WriteLine("\n\t{0} nu " + "implementeaza 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

Ioan Asiminoaei

");

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

}

}

class NameHiding2App

21.01.2007

27

{

 

public static void Main()

{

Console.WriteLine();

EditBox edit = new EditBox(); Console.WriteLine("Calling EditBox.Bind()

");

// 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

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

21.01.2007

28

{

 

public static void Main()

{

Test test = new Test();

Console.WriteLine("Calling Test.SaveData()"); test.SaveData(); // aici apare ambiguitatea

}

}

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

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

Rezolvare problema

using System;

interface ISerializable

{

void SaveData();

}

interface IDataStore

{

void SaveData();

}

21.01.2007

29

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();

}

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

Interfete si mostenirea

Cateva scenarii posibile

21.01.2007

30

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()

{

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(); // Care metoda Serialize se executa?

}

}

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, implementeaza.

Vezi exemplu:

using System;

public class Control

{

public void Serialize()

dar

nu ca

o

si

{ Console.WriteLine("Control.Serialize called");

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

}

public interface IDataBound

{ void Serialize();

}

21.01.2007

31

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();

");

 

}

 

else

 

{

 

Console.WriteLine("IDataBound is NOT supported

");

 

}

}

}

2. Clasa derivata din clasa 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");

}

}

Ioan Asiminoaei

Atribute si interfete Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi

public class InterfaceInh3App

21.01.2007

32

{

 

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.

Ioan Asiminoaei