ЗНАЕТЕ ЛИ ВЫ?

The CallerLineNumber attribute



The System.Runtime.CompilerServices.CallerLineNumberAttribute is allowed on optional parameters when there is a standard implicit conversion (§6.3.1) from the constant value int.MaxValue to the parameter’s type. This ensures that any non-negative line number up to that value can be passed without error.

If a function invocation from a location in source code omits an optional parameter with the CallerLineNumberAttribute, then a numeric literal representing that location's line number is used as an argument to the invocation instead of the default parameter value.

If the invocation spans multiple lines, the line chosen is implementation-dependent.

Note that the line number may be affected by #line directives (§2.5.7).

The CallerFilePath attribute

The System.Runtime.CompilerServices.CallerFilePathAttribute is allowed on optional parameters when there is a standard implicit conversion (§6.3.1) from string to the parameter’s type.

If a function invocation from a location in source code omits an optional parameter with the CallerFilePathAttribute, then a string literal representing that location's file path is used as an argument to the invocation instead of the default parameter value.

The format of the file path is implementation-dependent.

Note that the file path may be affected by #line directives (§2.5.7).

The CallerMemberName attribute

The System.Runtime.CompilerServices.CallerMemberNameAttribute is allowed on optional parameters when there is a standard implicit conversion (§6.3.1) from string to the parameter’s type.

If a function invocation from a location within the body of a function member or within an attribute applied to the function member itself or its return type, parameters or type parameters in source code omits an optional parameter with the CallerMemberNameAttribute, then a string literal representing the name of that member is used as an argument to the invocation instead of the default parameter value.

For invocations that occur within generic methods, only the method name itself is used, without the type parameter list.

For invocations that occur within explicit interface member implementations, only the method name itself is used, without the preceding interface qualification.

For invocations that occur within property or event accessors, the member name used is that of the property or event itself.

For invocations that occur within indexer accessors, the member name used is that supplied by an IndexerNameAttribute (§17.5.2.1) on the indexer member, if present, or the default name Item otherwise.

For invocations that occur within declarations of instance constructors, static constructors, destructors and operators the member name used is implementation-dependent.

Attributes for Interoperation

Note: This section is applicable only to the Microsoft .NET implementation of C#.

Interoperation with COM and Win32 components

The .NET run-time provides a large number of attributes that enable C# programs to interoperate with components written using COM and Win32 DLLs. For example, the DllImport attribute can be used on a static extern method to indicate that the implementation of the method is to be found in a Win32 DLL. These attributes are found in the System.Runtime.InteropServices namespace, and detailed documentation for these attributes is found in the .NET runtime documentation.

Interoperation with other .NET languages

The IndexerName attribute

Indexers are implemented in .NET using indexed properties, and have a name in the .NET metadata. If no IndexerName attribute is present for an indexer, then the name Item is used by default. The IndexerName attribute enables a developer to override this default and specify a different name.

namespace System.Runtime.CompilerServices.CSharp
{
[AttributeUsage(AttributeTargets.Property)]
public class IndexerNameAttribute: Attribute
{
public IndexerNameAttribute(string indexerName) {...}

public string Value { get {...} }
}
}


Unsafe code

The core C# language, as defined in the preceding chapters, differs notably from C and C++ in its omission of pointers as a data type. Instead, C# provides references and the ability to create objects that are managed by a garbage collector. This design, coupled with other features, makes C# a much safer language than C or C++. In the core C# language it is simply not possible to have an uninitialized variable, a “dangling” pointer, or an expression that indexes an array beyond its bounds. Whole categories of bugs that routinely plague C and C++ programs are thus eliminated.

While practically every pointer type construct in C or C++ has a reference type counterpart in C#, nonetheless, there are situations where access to pointer types becomes a necessity. For example, interfacing with the underlying operating system, accessing a memory-mapped device, or implementing a time-critical algorithm may not be possible or practical without access to pointers. To address this need, C# provides the ability to write unsafe code.

In unsafe code it is possible to declare and operate on pointers, to perform conversions between pointers and integral types, to take the address of variables, and so forth. In a sense, writing unsafe code is much like writing C code within a C# program.

Unsafe code is in fact a “safe” feature from the perspective of both developers and users. Unsafe code must be clearly marked with the modifier unsafe, so developers can’t possibly use unsafe features accidentally, and the execution engine works to ensure that unsafe code cannot be executed in an untrusted environment.

Unsafe contexts

The unsafe features of C# are available only in unsafe contexts. An unsafe context is introduced by including an unsafe modifier in the declaration of a type or member, or by employing an unsafe-statement:

· A declaration of a class, struct, interface, or delegate may include an unsafe modifier, in which case the entire textual extent of that type declaration (including the body of the class, struct, or interface) is considered an unsafe context.

· A declaration of a field, method, property, event, indexer, operator, instance constructor, destructor, or static constructor may include an unsafe modifier, in which case the entire textual extent of that member declaration is considered an unsafe context.

· An unsafe-statement enables the use of an unsafe context within a block. The entire textual extent of the associated block is considered an unsafe context.

The associated grammar extensions are shown below. For brevity, ellipses (...) are used to represent productions that appear in preceding chapters.

class-modifier:
...
unsafe

struct-modifier:
...
unsafe

interface-modifier:
...
unsafe

delegate-modifier:
...
unsafe

field-modifier:
...
unsafe

method-modifier:
...
unsafe

property-modifier:
...
unsafe

event-modifier:
...
unsafe

indexer-modifier:
...
unsafe

operator-modifier:
...
unsafe

constructor-modifier:
...
unsafe

destructor-declaration:
attributesopt externopt unsafeopt ~ identifier ( ) destructor-body
attributesopt unsafeopt externopt ~ identifier ( ) destructor-body

static-constructor-modifiers:
externopt unsafeopt static
unsafeopt externopt static
externopt static unsafeopt
unsafeopt static externopt
static externopt unsafeopt
static unsafeopt externopt

embedded-statement:
...
unsafe-statement

unsafe-statement:
unsafe block

In the example

public unsafe struct Node
{
public int Value;
public Node* Left;
public Node* Right;
}

the unsafe modifier specified in the struct declaration causes the entire textual extent of the struct declaration to become an unsafe context. Thus, it is possible to declare the Left and Right fields to be of a pointer type. The example above could also be written

public struct Node
{
public int Value;
public unsafe Node* Left;
public unsafe Node* Right;
}

Here, the unsafe modifiers in the field declarations cause those declarations to be considered unsafe contexts.

Other than establishing an unsafe context, thus permitting the use of pointer types, the unsafe modifier has no effect on a type or a member. In the example

public class A
{
public unsafe virtual void F() {
char* p;
...
}
}

public class B: A
{
public override void F() {
base.F();
...
}
}

the unsafe modifier on the F method in A simply causes the textual extent of F to become an unsafe context in which the unsafe features of the language can be used. In the override of F in B, there is no need to re-specify the unsafe modifier—unless, of course, the F method in B itself needs access to unsafe features.

The situation is slightly different when a pointer type is part of the method’s signature

public unsafe class A
{
public virtual void F(char* p) {...}
}

public class B: A
{
public unsafe override void F(char* p) {...}
}

Here, because F’s signature includes a pointer type, it can only be written in an unsafe context. However, the unsafe context can be introduced by either making the entire class unsafe, as is the case in A, or by including an unsafe modifier in the method declaration, as is the case in B.

Pointer types

In an unsafe context, a type (§4) may be a pointer-type as well as a value-type or a reference-type. However, a pointer-type may also be used in a typeof expression (§7.6.10.6) outside of an unsafe context as such usage is not unsafe.

type:
...
pointer-type

A pointer-type is written as an unmanaged-type or the keyword void, followed by a * token:

pointer-type:
unmanaged-type *
void *

unmanaged-type:
type

The type specified before the * in a pointer type is called the referent type of the pointer type. It represents the type of the variable to which a value of the pointer type points.

Unlike references (values of reference types), pointers are not tracked by the garbage collector—the garbage collector has no knowledge of pointers and the data to which they point. For this reason a pointer is not permitted to point to a reference or to a struct that contains references, and the referent type of a pointer must be an unmanaged-type.

An unmanaged-type is any type that isn’t a reference-type or constructed type, and doesn’t contain reference-type or constructed type fields at any level of nesting. In other words, an unmanaged-type is one of the following:

· sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool.

· Any enum-type.

· Any pointer-type.

· Any user-defined struct-type that is not a constructed type and contains fields of unmanaged-types only.

The intuitive rule for mixing of pointers and references is that referents of references (objects) are permitted to contain pointers, but referents of pointers are not permitted to contain references.

Some examples of pointer types are given in the table below:

 

Example Description
byte* Pointer to byte
char* Pointer to char
int** Pointer to pointer to int
int*[] Single-dimensional array of pointers to int
void* Pointer to unknown type

 

For a given implementation, all pointer types must have the same size and representation.

Unlike C and C++, when multiple pointers are declared in the same declaration, in C# the * is written along with the underlying type only, not as a prefix punctuator on each pointer name. For example

int* pi, pj; // NOT as int *pi, *pj;

The value of a pointer having type T* represents the address of a variable of type T. The pointer indirection operator * (§18.5.1) may be used to access this variable. For example, given

a variable P of type int*, the expression *P denotes the int variable found at the address contained in P.

Like an object reference, a pointer may be null. Applying the indirection operator to a null pointer results in implementation-defined behavior. A pointer with value null is represented by all-bits-zero.

The void* type represents a pointer to an unknown type. Because the referent type is unknown, the indirection operator cannot be applied to a pointer of type void*, nor can any arithmetic be performed on such a pointer. However, a pointer of type void* can be cast to any other pointer type (and vice versa).

Pointer types are a separate category of types. Unlike reference types and value types, pointer types do not inherit from object and no conversions exist between pointer types and object. In particular, boxing and unboxing (§4.3) are not supported for pointers. However, conversions are permitted between different pointer types and between pointer types and the integral types. This is described in §18.4.

A pointer-type cannot be used as a type argument (§4.4), and type inference (§7.5.2) fails on generic method calls that would have inferred a type argument to be a pointer type.

A pointer-type may be used as the type of a volatile field (§10.5.3).

Although pointers can be passed as ref or out parameters, doing so can cause undefined behavior, since the pointer may well be set to point to a local variable which no longer exists when the called method returns, or the fixed object to which it used to point, is no longer fixed. For example:

using System;

class Test
{
static int value = 20;

unsafe static void F(out int* pi1, ref int* pi2) {
int i = 10;
pi1 = &i;

fixed (int* pj = &value) {
// ...
pi2 = pj;
}
}

static void Main() {
int i = 10;
unsafe {
int* px1;
int* px2 = &i;

F(out px1, ref px2);

Console.WriteLine("*px1 = {0}, *px2 = {1}",
*px1, *px2); // undefined behavior
}
}
}

A method can return a value of some type, and that type can be a pointer. For example, when given a pointer to a contiguous sequence of ints, that sequence’s element count, and some other int value, the following method returns the address of that value in that sequence, if a match occurs; otherwise it returns null:

unsafe static int* Find(int* pi, int size, int value) {
for (int i = 0; i < size; ++i) {
if (*pi == value)
return pi;
++pi;
}
return null;
}

In an unsafe context, several constructs are available for operating on pointers:

· The * operator may be used to perform pointer indirection (§18.5.1).

· The -> operator may be used to access a member of a struct through a pointer (§18.5.2).

· The [] operator may be used to index a pointer (§18.5.3).

· The & operator may be used to obtain the address of a variable (§18.5.4).

· The ++ and -- operators may be used to increment and decrement pointers (§18.5.5).

· The + and - operators may be used to perform pointer arithmetic (§18.5.6).

· The ==, !=, <, >, <=, and => operators may be used to compare pointers (§18.5.7).

· The stackalloc operator may be used to allocate memory from the call stack (§18.7).

· The fixed statement may be used to temporarily fix a variable so its address can be obtained (§18.6).





Последнее изменение этой страницы: 2016-12-14; Нарушение авторского права страницы

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.216.79.60 (0.015 с.)