Sie sind auf Seite 1von 16

Top 10 Most Important Features Of C#

Here is the list of the 10 most important things in C#.


Advantages of C#
Future of C#
Abstraction in C#
Encapsulation in C#
Inheritance in C#
Polymorphism in C#
Delegates in C#
Collections in C#
Exception handling in C#
File Handling in C#
1. Advantages of C# over Java

I have found some interesting advantages of C# over Java and for that I have also
won the prize from the Java team...!!. So I want to share it with all people so
others can also know about it.
C# being a .NET language, it supports language interoperability, i.e. C# can access
code written in any .NET compliant language and can also inherit the classes
written in these languages. This is not possible in Java.

The code written in C#, on compilation generates an ‘.exe' or ‘.dll' file which is
also called Portable Executable file. These files contain MSIL (Microsoft
Intermediate Language) code. As against this, the Java code on compilation
generates a ‘.class' file, which contains bytecode.

The portable executable file of C# can contain any number of classes, whereas, the
‘.class' file in Java contains only one class.

The methods in C# are not virtual by default. On the contrary, methods in Java are
virtual by default, which degrades the performance.

The classes in C# are grouped in Namespaces, whereas, classes in Java are grouped
in Packages.

The C# namespaces are not related to the directories. The packages in Java are
directly related with the directory names.

The variables of primitive data types in C# are more powerful. This is because even
though they are not objects functions can be called using them. The variables of
primitive data types in Java cannot call functions.

C# has features like Properties and Indexers. These features are not available in
the Java language.

C# supports Structures, Operator Overloading and Pre-processors directives,


whereas, Java has none of them.

Through C# we can easily call Windows API function and access COM components which
is quite difficult in Java.
For more follow the link:
Advantages of C# Over Java

2. Future of C#

Today, C# is not only a Windows development programming language but can be used to
build Web applications, Windows store apps, and mobile apps including iOS and
Android. C# can also do more than that. If you’ve not already read my article, I
highly recommend going and reading What C# Can Do For You.

At the Build 2016 event, Microsoft made several exciting announcements and one of
them was integrating Xamarin as a part of Visual Studio “15” and beyond. Now C#
developers can build iOS and Android apps that can spit out native iOS and Android
code.

...the future of C# is very bright.

In the following Channel 9 video, Microsoft’s Dustin Campbell and Mads Torgersen
talk about the future of C#.

Here are some of the bullet points from the video:


You can write C# in any editor you want.

C# is open source now

C# runs on Windows, Mac, and Linux

C# can be used to build Windows client apps, Windows Store apps, iOS apps, and
Android aps and can also be used to build backend and middle-tier frameworks and
libraries.

C# (via Roslyn, the C# engine):

Supports all IDEs and editors

All the linters and analysis tools

All the fixing and refactoring and code generation tools

All the scripting and all the REPLs

C# 7 comes with new features including tuples, record types, and pattern matching.

C# is young and evolving.

Unlike other programming languages, C# is still young and evolving. Now being open
sourced, C# is getting community involvement and new features are being decided by
community. The following table summarizes the improvements made in each newer
version of the language.

Version
Year
Key features introduced
1.0
Jan 2002

1.2
Oct 2003
Modern, object-oriented, type safe, automatic memory management, versioning control
2.0
Sept 2005
Generics, partial classes, anonymous types, iterators, nullable types, static
classes, delegate interface
3.0
Aug 2007
Implicit types, object and collection initializers, auto-implemented properties,
extension methods, query and lambda expressions, expression trees, partial methods.
4.0
April 2010
Dynamic binding, named and optional arguments, Generic covariance and
Contravariance, Embedded interop types.
5.0
June 2013
Async methods, Caller info Attributes
6.0
July 2015
Roslyn (compiler-as-a-service), exception filters, Await in catch/finally block,
auto property initializer, string interpolation, nameof operator, dictionary
initializer
7.0
2016
Tuples, pattern matching, record types, local functions, Async streams
For more follow the link:

What Is The Future Of C#


3. Abstraction in C#

The word abstract means a concept or an idea not associated with any specific
instance. In programming we apply the same meaning of abstraction by making classes
not associated with any specific instance. The abstraction is done when we need to
only inherit from a certain class, but do not need to instantiate objects of that
class. In such case the base class can be regarded as "Incomplete". Such classes
are known as an "Abstract Base Class".

Abstract Base Class

There are some important points about Abstract Base Class :


An Abstract Base class can not be instantiated; it means the object of that class
can not be created.

Class having abstract keyword and having abstract keyword with some of its methods
(not all) is known as an Abstract Base Class.

Class having Abstract keyword and having abstract keyword with all of its methods
is known as pure Abstract Base Class.

The method of abstract class that has no implementation is known as "operation". It


can be defined as abstract void method ();

An abstract class holds the methods but the actual implementation of those methods
is made in derived class.
Lets have a look at this code!

abstract class animal


{
public abstract void eat();
public void sound()
{
Console.WriteLine("dog can sound");
}
}
This is the Abstract Base Class, if I make both of its methods abstract then this
class would become a pure Abstract Base Class.

Now we derive a class of 'dog' from the class animal.


abstract class animal
{
public abstract void eat();
public void sound()
{
Console.WriteLine("dog can sound");
}
}
class dog: animal
{
public override void eat()
{
Console.WriteLine("dog can eat");
}
}
Here you can see we have 2 methods in the Abstract Base Class, the method eat() has
no implementation; that is why it is being declared as 'abstract' while the method
sound() has its own body so it is not declared as 'abstract'.

In the derived class we have the same named method but this method has its body.

We are doing abstraction here so that we can access the method of derived class
without any trouble.

Let's have a look!

class program
{
abstract class animal
{
public abstract void eat();
public void sound()
{
Console.WriteLine("dog can sound");
}
}
class dog: animal
{
public override void eat()
{
Console.WriteLine("dog can eat");
}
}
static void Main(string[] args)
{
dog mydog = new dog();
animal thePet = mydog;
thePet.eat();
mydog.sound();
}
}
Finally we created an Object 'mydog' of class dog, but we didn't instantiate any
object of Abstract Base Class 'animal'.

According to "Ivor Horton" (a programmer of Java) an object can not be


instantiated, but we can declare a variable of the Abstract Class type. If this
statement is true then it could be possible:
animal thePet;

This is an object which is declared as thePet and its data type is the abstract
base class 'animal'.

We can use this Object to store Objects of the subclass.

In the above code we declare an Object 'thePet', of the type animal (the Abstract
Base Class) and simply copy the object of another object (only the reference is
copied as they belong to reference type). Now we can use object 'thePet' just as
object 'mydog'.

The output of this code would be,

dog can eat


dog can sound

For more follow the link:


Abstraction in C#
4. Encapsulation in C#

The object oriented programming will give the impression very unnatural to a
programmer with a lot of procedural programming experience. In Object Oriented
programming Encapsulation is the first place. Encapsulation is the procedure of
covering up of data and functions into a single unit (called class). An
encapsulated object is often called an abstract data type. In this article let us
see about it in a detailed manner.

NEED FOR ENCAPSULATION

The need of encapsulation is to protect or prevent the code (data) from accidental
corruption due to the silly little errors that we are all prone to make. In Object
oriented programming data is treated as a critical element in the program
development and data is packed closely to the functions that operate on it and
protects it from accidental modification from outside functions.

Encapsulation provides a way to protect data from accidental corruption. Rather


than defining the data in the form of public, we can declare those fields as
private. The Private data are manipulated indirectly by two ways. Let us see some
example programs in C# to demonstrate Encapsulation by those two methods. The first
method is using a pair of conventional accessor and mutator methods. Another one
method is using a named property. Whatever be the method our aim is to use the data
without any damage or change.

ENCAPSULATION USING ACCESSORS AND MUTATORS

Let us see an example of Department class. To manipulate the data in that class
(String departname) we define an accessor (get method) and mutator (set method).

using system;
public class Department
{
private string departname;
.......
// Accessor.
public string GetDepartname()
{
return departname;
}
// Mutator.
public void SetDepartname(string a)
{
departname = a;
}
}
Like the above way we can protect the private data from the outside world. Here we
use two separate methods to assign and get the required data.

public static int Main(string[] args)


{
Department d = new Department();
d.SetDepartname("ELECTRONICS");
Console.WriteLine("The Department is :" + d.GetDepartname());
return 0;
}
In the above example we can't access the private data departname from an object
instance. We manipulate the data only using those two methods.

For more follow the link:


Encapsulation in C#
5. Inheritance in C#

Inheritance is one of the three foundational principles of Object-Oriented


Programming (OOP) because it allows the creation of hierarchical classifications.
Using inheritance you can create a general class that defines traits common to a
set of related items. This class can then be inherited by other, more specific
classes, each adding those things that are unique to it.

In the language of C#, a class that is inherited is called a base class. The class
that does the inheriting is called the derived class. Therefore a derived class is
a specialized version of a base class. It inherits all of the variables, methods,
properties, and indexers defined by the base class and adds its own unique
elements.

Example

Diagram

The following diagram shows the inheritance of a shape class. Here the base class
is Shape and the other classes are its child classes. In the following program we
will explain the inheritance of the Triangle class from the Shape class.

//Base class or Parent class.


class Shape
{
public double Width;
public double Height;
public void ShowDim()
{
Console.WriteLine("Width and height are " +
Width + " and " + Height);
}
}
// Triangle is derived from Shape.
//Drived class or Child class.
class Triangle: Shape
{
public string Style; // style of triangle
// Return area of triangle.
public double Area()
{
return Width * Height / 2;
}
// Display a triangle's style.
public void ShowStyle() {
Console.WriteLine("Triangle is " + Style);
}
}
//Driver class which runs the program.
class Driver
{
static void Main()
{
Triangle t1 new Triangle();
Triangle t2 new Triangle();
t1.Width = 4.0;
t1.Height = 4.0;
t1.Style = "isosceles";
t2.Width = 8.0;
t2.Height = 12.0;
t2.Style = "right";
Console.WriteLine("Info for t1: ");
t1.ShowStyle();
t1.ShowDim();
Console.WriteLine("Area is " + t1.Area());
Console.WriteLine();
Console.WriteLine("Info for t2: ");
t2.ShowStyle();
t2.ShowDim();
Console.WriteLine("Area is " + t2.Area());
}
}
The output from this program is shown here:

Info for t1
Triangle is isosceles
Width and height are 4 and 4
Area is 8
Info for t2:
Triangle is right
Width and height are 8 and 12
Area is 48

For more follow the link:


Inheritance With Example in C#
6. Polymorphism in C#

Polymorphism means the same operation may behave differently on different classes.

Example of Compile Time Polymorphism: Method Overloading

Example of Run Time Polymorphism: Method Overriding

Example of Compile Time Polymorphism


Method Overloading: Method with same name but with different arguments is called
method overloading.

Method Overloading forms compile-time polymorphism.


Example of Method Overloading

class A1
{
void hello()
{
Console.WriteLine("Hello");
}
void hello(string s)
{
Console.WriteLine("Hello {0}", s);
}
}
Example of Run Time Polymorphism
Method Overriding: Method overriding occurs when child class declares a method that
has the same type arguments as a method declared by one of its superclass.

Method overriding forms Run-time polymorphism.

Note

By default functions are not virtual in C# and so you need to write “virtual”
explicitly. While by default in Java each function are virtual.

Example of Method Overriding

Class parent
{
virtual void hello()
{
Console.WriteLine("A D Patel");
}
}
Class child: parent
{
override void hello()
{
Console.WriteLine("R A Patel");
}
}
static void main()
{
parent objParent = new child();
objParent.hello();
}
//Output
R A Patel
For more follow the link:
Polymorphism in C#
7. Delegates in C#

Delegate is one of the base types in .NET. Delegate is a class, which is used to
create delegate at runtime.

Delegate in C# is similar to a function pointer in C or C++. It's a new type of


object in C#. Delegate is very special type of object as earlier the entire the
object we used to defined contained data but delegate just contains the details of
a method.

Need of delegate

There might be a situation in which you want to pass methods around to other
methods. For this purpose we create delegate.

A delegate is a class that encapsulates a method signature. Although it can be used


in any context, it often serves as the basis for the event-handling model in C# but
can be used in a context removed from event handling (e.g. passing a method to a
method through a delegate parameter).

One good way of understanding delegates is by thinking of a delegate as something


that gives a name to a method signature.

Example

public delegate int DelegateMethod(int x, int y);


Any method that matches the delegate's signature, which consists of the return type
and parameters, can be assigned to the delegate. This makes is possible to
programmatically change method calls, and also plug new code into existing classes.
As long as you know the delegate's signature, you can assign your own-delegated
method.

This ability to refer to a method as a parameter makes delegates ideal for defining
callback methods.

Delegate magic

In class we create its object, which is instance, but in delegate when we create
instance that is also referred to as delegate (means whatever you do you will get
delegate).

Delegate does not know or care about the class of the object that it references.
Any object will do; all that matters is that the method's argument types and return
type match the delegate's. This makes delegates perfectly suited for "anonymous"
invocation.

Benefit of delegates

In simple words delegates are object oriented and type-safe and very secure as they
ensure that the signature of the method being called is correct. Delegate helps in
code optimization.

Types of delegates
Singlecast delegates
Multiplecast delegates
Delegate is a class. Any delegate is inherited from base delegate class of .NET
class library when it is declared. This can be from either of the two classes from
System.Delegate or System.MulticastDelegate.

Singlecast delegate

Singlecast delegate point to single method at a time. In this the delegate is


assigned to a single method at a time. They are derived from System.Delegate class.

Multicast Delegate
When a delegate is wrapped with more than one method that is known as a multicast
delegate.

In C#, delegates are multicast, which means that they can point to more than one
function at a time. They are derived from System.MulticastDelegate class.

For more follow the link:


C# Delegates
8. Collections in C#

“.NET” offers a variety of collections, such as ArrayList, Hashtable, queues,


Dictionaries. Collections are abstractions of data algorithms. An ArrayList is an
abstract dynamic array, a Hashtable collection abstracts a lookup table, a Queues
collection abstracts queues and so on. In addition to that, collections implement
the ICollection, IEnumerable and IClonable interfaces. The detailed specification
for each collection is found under the System.Collection namespace.

ArrayList Collection

An ArrayList is a dynamic array and implements the IList interface. Each element is
accessible using the indexing operator. While the traditional array has a fixed
number of elements, in the case of Array List, elements can be added or removed at
run time.

We can create an object of ArrayList using a general type of syntax as follows;

ArrayList obj = new ArrayList();

Here you can use the new keyword to create an instance of the ArrayList object. You
don't need to specify the size. Once you have an empty ArrayList object, you can
use the Add() method to add elements to it,as in,

obj.Add("item1");
obj.Add("2");
obj.Add("Delhi");
Each new item in the ArrayList is added to the end of the list, so it has the
largest index number. If you wanted to add an item in the middle of the list, then
you can use "Insert()" with a numeric argument as follows,

Obj.Insert(2,"item2");
You can also remove members of the ArrayList using either Remove() or RemoveAt()
methods as in the following,

Obj.Remove("item1");
Obj.RemoveAt(3);
Benefits of ArrayLists
Insert Elements: An ArrayList starts with a collection containing no elements. You
can add them in any position as you choose them.

Automatic Resizing: you do not need to specify the array size; as you add elements,
the array automatically ensures there is enough memory for the array.

Flexibility When Removing Elements: you can remove any element from an Arraylist
very easily.
Limitations of ArrayLists

The flexibility of ArrayList comes at a cost of performance. Since memory


allocation is a very expensive business, the fixed number of elements of the simple
array makes it much faster to work with.

Note - ArrayLists are slower and more resource-intensive than a conventional array.

Simple ArrayList Example

The following example shows an array list with an #ff0000 size. Elements are added
dynamically as required. We are adding elements using the "Add()" method as well as
using the "Insert()" method at a specific location. Later we are displaying all the
elements by iterating through the list.

using System;
using System.Collections;
namespace CollectionApp
{
class Program
{
static void Main(string[] args)
{
//Defining an ArrayList
ArrayList obj = new ArrayList();
//Adding elements
obj.Add("India");
obj.Add("USA");
obj.Add("Russia");
obj.Add("300");
//Adding elements to specific position
obj.Insert(2, "Japan");
//Accessing elements
for (int i = 0; i < obj.Count; i++)
{
Console.WriteLine("At Index[" + i + "]= " + obj[i].ToString());
}
Console.WriteLine("____________");
Console.WriteLine("Press any key");
Console.ReadKey();
}
}
}
After building and running this program, the output of the program is as in the
following,

For more Collections follow the link:


Using .NET Collections With C#

9. Exception handling in C#

Exception handling is a built-in mechanism in .NET framework to detect and handle


run time errors. The .NET framework contains many standard exceptions. The
exceptions are anomalies that occur during the execution of a program. They can be
because of user, logic or system errors. If a user (programmer) does not provide a
mechanism to handle these anomalies, the .NET run time environment provides a
default mechanism that terminates the program execution.

C# provides the three keywords try, catch and finally to do exception handling. The
try block encloses the statements that might throw an exception whereas catch
handles an exception if one exists. The finally can be used for doing any clean-up
process.

The general form of try-catch-finally in C# is shown below.

try
{
// Statement which can cause an exception.
} catch (Type x)
{
// Statements for handling the exception
} finally
{
//Any cleanup code
}
If any exception occurs inside the try block then the control transfers to the
appropriate catch block and later to the finally block.

But in C#, both catch and finally blocks are optional. The try block can exist
either with one or more catch blocks or a finally block or with both catch and
finally blocks.

If there is no exception occurring inside the try block then the control directly
transfers to the finally block. We can say that the statements inside the finally
block is executed always. Note that it is an error to transfer control out of a
finally block by using break, continue, return or goto.

In C#, exceptions are nothing but objects of the type Exception. The Exception is
the ultimate base class for any exceptions in C#. The C# itself provides a couple
of standard exceptions. Or even the user can create their own exception classes,
provided that this should inherit from either the Exception class or one of the
standard derived classes of the Exception class like DivideByZeroExcpetion or
ArgumentException and so on.

For more follow the link:


Exception Handling in C#
10. File Handling in C#

The System.IO namespace provides four classes that allow you to manipulate
individual files, as well as interact with a machine directory structure. The
Directory and File directly extends System.Object and supports the creation,
copying, moving and deletion of files using various static methods. They only
contain static methods and are never instantiated. The FileInfo and DirecotryInfo
types are derived from the abstract class FileSystemInfo type and they are
typically, employed for obtaining the full details of a file or directory because
their members tend to return strongly typed objects. They implement roughly the
same public methods as a Directory and a File but they are stateful and the members
of these classes are not static.

In the .NET framework, the System.IO namespace is the region of the base class
libraries devoted to file based input and output services. Like any namespace, the
System.IO namespace defines a set of classes, interfaces, enumerations, structures
and delegates. The following table outlines the core members of this namespace.

Class Types
Description
Directory/ DirectoryInfo
These classes support the manipulation of the system directory structure.
DriveInfo
This class provides detailed information regarding the drives that a given machine
has.
FileStream
This gets you random file access with data represented as a stream of bytes.
File/FileInfo
These sets of classes manipulate a computer's files.
Path
It performs operations on System.String types that contain file or directory path
information in a platform-neutral manner.
BinaryReader/ BinaryWriter
These classes allow you to store and retrieve primitive data types as binary
values.
StreamReader/StreamWriter
Used to store textual information to a file.
StringReader/StringWriter
These classes also work with textual information. However, the underlying storage
is a string buffer rather than a physical file.
BufferedStream
This class provides temp storage for a stream of bytes that you can commit to
storage at a later time.
The System.IO provides a class DriveInfo to manipulate the system drive related
tasks. The DriveInfo class provides numerous details such as the total number of
drives, calculation of total hard disk space, available space, drive name, ready
status, types and so on. Consider the following program that shows the total disk
drives.

DriveInfo[] di = DriveInfo.GetDrives();
Console.WriteLine("Total Partitions");

foreach(DriveInfo items in di)


{
Console.WriteLine(items.Name);
}
The following code snippets perform the rest of the DriveInfo class method
operations in details. It displays specific drive full information.

using System;
using System.IO;

namespace DiskPartition
{
class Program
{
static void Main(string[] args)
{
DriveInfo[] di = DriveInfo.GetDrives();

Console.WriteLine("Total Partitions");
Console.WriteLine("---------------------");
foreach(DriveInfo items in di)
{
Console.WriteLine(items.Name);
}
Console.Write("\nEnter the Partition::");
string ch = Console.ReadLine();

DriveInfo dInfo = new DriveInfo(ch);


Console.WriteLine("\n");

Console.WriteLine("Drive Name::{0}", dInfo.Name);


Console.WriteLine("Total Space::{0}", dInfo.TotalSize);
Console.WriteLine("Free Space::{0}", dInfo.TotalFreeSpace);
Console.WriteLine("Drive Format::{0}", dInfo.DriveFormat);
Console.WriteLine("Volume Label::{0}", dInfo.VolumeLabel);
Console.WriteLine("Drive Type::{0}", dInfo.DriveType);
Console.WriteLine("Root dir::{0}", dInfo.RootDirectory);
Console.WriteLine("Ready::{0}", dInfo.IsReady);

Console.ReadKey();
}
}
}
After compiling this program, it displays nearly every detail of disk drives and a
specific drive as in the following,

Reading and Writing to Files

Reading and writing operations are done using a File object. The following code
snippet reads a text file located in the machine somewhere.

private void button1_Click(object sender, EventArgs e)


{
try
{
textBox2.Text = File.ReadAllText(txtPath.Text);
} catch (FileNotFoundException)
{
MessageBox.Show("File not Found....");
}
}
Here, first the user interface asks the user to enter the path of the file that he
wanted to display. Later that path is passed to the File method ReadAllText()
method that reads all the text integrated in the file and displays it over the text
box.

Besides reading a file, we can write some contents over an existing text file by
the File class WriteAllTest() method as in the following,

File.WriteAllText(@"d:\test.txt", textBox2.Text);
It takes a path to save the file and content input method medium such as a text box
or any other control. The following images depict a text file reading by entering
its corresponding path,

Stream

The .NET provides many objects such as FileStream, StreamReader/Writer,


BinaryReader/Writer to read from and write data to a file. A stream basically
represents a chunk of data flowing between a source and a destination. Stream
provides a common way to interact with a sequence of bytes regardless of what kind
of devices store or display the bytes. The following table provides common stream
member functions,
Methods
Description
Read()/ ReadByte()
Read a sequence of bytes from the current stream.
Write()/WriteByte()
Write a sequence of bytes to the current stream.
Seek()
Sets the position in the current stream.
Position()
Determine the current position in the current stream.
Length()
Return the length of the stream in bytes.
Flush()
Updates the underlying data source with the current state of the buffer and then
clears the buffer.
Close()
Closes the current stream and releases any associated stream resources.
FileStream

A FileStream instance is used to read or write data to or from a file. In order to


construct a FileStream, first we need a file that we want to access. Second, the
mode that indicates how we want to open the file. Third, the access that indicates
how we want to access a file. And finally, the share access that specifies whether
you want exclusive access to the file.
Enumeration
Values
FileMode
Create, Append, Open, CreateNew, Truncate, OpenOrCreate
FileAccess
Read, Write, ReadWrite
FileShare
Inheritable, Read, None, Write, ReadWrite
The FileStream can read or write only a single byte or an array of bytes. You will
be required to encode the System.String type into a corresponding byte array. The
System.Text namespace defines a type named encoding that provides members that
encode and decode strings to an array of bytes. Once encoded, the byte array is
persisted to a file with the FileStream.Write() method. To read the bytes back into
memory, you must reset the internal position of the stream and call the ReadByte()
method. Finally, you display the raw byte array and the decoded string to the
console.

using(FileStream fs = new FileStream(@ "d:\ajay123.doc", FileMode.Create))


{
string msg = "first program";
byte[] byteArray = Encoding.Default.GetBytes(msg);
fs.Write(byteArray, 0, byteArray.Length);
fs.Position = 0;

byte[] rFile = new byte[byteArray.Length];

for (int i = 0; i < byteArray.Length; i++)


{
rFile[i] = (byte) fs.ReadByte();
Console.WriteLine(rFile[i]);
}

Console.WriteLine(Encoding.Default.GetString(rFile));
}
BinaryReader and BinaryWriter
The BinaryReader and Writer class allows you to read and write discrete data types
to an underlying stream in a compact binary format. The BinaryWriter class defines
a highly overloaded Write method to place a data type in the underlying stream.

Members
Description
Class
Write
Write the value to current stream
BinaryWriter
Seek
Set the position in the current stream
BinaryWriter
Close
Close the binary reader
BinaryWriter
Flush
Flush the binary stream
BinaryWriter
PeekChar
Return the next available character without advancing the position in the stream
BinaryReader
Read
Read a specified set of bytes or characters and store them in the incoming array.
BinaryReader
For more examples follow the link:
File Handling in C# .NET

Das könnte Ihnen auch gefallen