left-icon

Customer Success for C# Developers Succinctly®
by Ed Freitas

Previous
Chapter

of
A
A
A

CHAPTER 4

Reflection to the Rescue

Reflection to the Rescue


Introduction

The Reflection mechanism in the .NET Framework is an extremely powerful feature. However, most developers find themselves asking when they will ever use it, and why use it at all.

At first glance, Reflection can look scary, as it mostly falls into the category of reverse engineering—a technology often used by people writing decompilers and other developer tools.

Although there is truth in that perception, Reflection offers many extremely useful features, and it can prove to be invaluable for troubleshooting or writing plugins for existing applications.

Some of these features include discovering types in assemblies, dynamic loading with configuration, and better understanding of how .NET assemblies work internally. By gaining an improved understanding of these items, we can become better at troubleshooting, leading to faster and better customer service. Faster incident turnaround and resolution is the ultimate goal.

We’ll also explore how some tools built around Reflection can be used to inspect code when we don’t have the source code at hand, which can be essential for achieving faster incident resolution. By inspecting the code of a problematic DLL, for instance, we can narrow the location of a problem within the code much more quickly.

The goal of this chapter is to explore the practical parts of Reflection with an emphasis on safety, flexibility, performance, and proper ethical use so that you can put this extremely powerful feature to work and gain the benefits of diagnosing problems more quickly, speeding up incident-resolution time and allowing you to better understand how to build plugins for existing applications.

What is Reflection?

In .NET, Reflection consists of programmatically inspecting the metadata and compiled code within a .NET assembly. To better understand this, we need to look at each of these concepts separately.

An assembly in the .NET world is a logical unit of execution, deployment, and reuse. It can be either a library (DLL) or executable (EXE). Inside an assembly are one or more independent units called modules.

The main module of an assembly contains what is known as the assembly manifest, which includes information such as the assembly name, version, culture, and lists of all the module parts of the assembly itself.

The metadata of the module contains a complete description of all the types exposed, which includes available methods, properties, fields, parameters, constructors, etc. An essential part of this metadata is the Common Intermediate Language (CIL) compiled from the source code. Assemblies can also contain resources, which can include images, text files, and strings for localization.

Assemblies can be discussed from many perspectives, but because Reflection works with metadata and CIL, that’s what we’ll focus on.

Assemblies are essentially self-describing, and they contain all the necessary information for the .NET runtime to execute the code. This contains information about the assembly being executed, other assemblies referenced by the assembly, all of the types within the assembly, and the CIL code.

Using a free tool called ILSpy, we can dig into the details of any assembly, explore its metadata, and automatically decompile its source code from CIL to its original, high-level .NET language. Visual Studio ships with a Spy++ program, but be aware that ILSpy is very similar to .NET Reflector from Red Gate Software, which is easy and intuitive to use.

The CIL code is the actual runnable part of the assembly. Rather than compiling a high-level, human-understandable language such as C# to machine code, .NET takes an intermediate step, with the compiler generating CIL code that sits somewhere between human-readable code and machine code. CIL is somewhat familiar with other low-level languages.

In .NET, assemblies are compiled just-in-time (JIT), which means that compilation is actually done during the execution of the program rather than prior to execution. Essentially, the actual machine code is not generated until the application is executed. The .NET runtime takes care of converting the CIL into OS instructions that the CPU can understand. This is advantageous because the code is compiled for the machine it is running on (it can actually be optimized for that particular machine), and because only parts of the code that are actually run get compiled. So parts of the application that are not used will remain in CIL until executed.

ILSpy requires no installation. It is a zipped archive that contains an executable and a set of DLLs when uncompressed, which means it can be used to inspect any .NET assembly.

ILSpy after Downloading and Unzipping

Figure 20: ILSpy after Downloading and Unzipping

With ILSpy, let’s quickly inspect the metadata of the Newtonsoft.Json assembly we previously used in our Simple Awesome CRM project.

Assembly Metadata Properties Seen with ILSpy

Figure 21: Assembly Metadata Properties Seen with ILSpy

Based on the metadata, Reflection allows us to dynamically create types, invoke methods, get and set the values of properties, and inspect attributes. The System.Reflection namespace offers all the necessary .NET classes and methods needed to perform all of these actions.

Arguably the most important feature of the System.Reflection namespace is the Type class. This class has more than 150 members, including static instance members and properties. Some of the most common methods in the Type class include the Get methods, such as GetType, GetMemberInfo, GetPropertyInfo, GetFieldInfo, and many more.

The Activator class is another extremely important class within the System.Reflection namespace. The Activator class has a set of static methods that can be used to create an instance of any specific type, such as the CreateInstance method.

The Assembly class can be used to load an assembly from a file or the current program context. Once the assembly is loaded, we can use Reflection to see which types are available. The Assembly class contains various useful methods, such as Load, LoadFrom, GetTypes, GetName, GetFiles, etc.

There is also an ILGenerator class that can be used to create our own CIL code. While it is useful to know that this class exists, that topic goes far beyond the scope of this chapter and e-book.

Going forward, we’ll focus on the aspects of Reflection that are useful in everyday application development and can be helpful for troubleshooting. We won’t specifically focus on features more suited for creation of advanced software development or reverse engineering tools like obfuscation.

Speed and consistency

Reflection can be well suited for daily application development and troubleshooting. But what can be done and what should be done are not always the same thing.

Prior to the introduction of the dynamic keyword in .NET 4.0, the only way to interact with properties on COM objects was to do Reflection on the object itself. So what could be done with Reflection prior to .NET 4.0 in order to get the value of a COM object property should no longer be done in .NET 4.0 and beyond.

It is also feasible to perform Reflection on a private field (but it's usually not a good idea) and also on any particular method.

You can also get a CLR type based on the type name (using the Type class), create an instance of a type (using the Activator class), load an assembly based on the file name (using the Assembly class, which is commonly used when designing applications that allow plugins), get the types exposed on an assembly (using the Assembly class), or check to see if a Type implements an Interface (using the Type class).

The key point to understand here is that you should only use Reflection when you really need it. Let’s explore a few examples that show some drawbacks regarding speed and accuracy when using Reflection. We’ll do this by analyzing how we can create a List with and without Reflection. Then we’ll compare the speed of both.

Code Listing 27: Speed Comparison with and without Reflection

using System.Diagnostics;

using System;

using System.Collections.Generic;

namespace ReflectionExamples

{

    class Program

    {

        static void Main(string[] args)

        {

            CreateListNormally();

            CreateListReflection();

            Console.ReadLine();

        }

        public static void CreateListNormally()

        {

            Stopwatch sw = Stopwatch.StartNew();

            for(int i = 0; i <= 1000; i++)

            {

                List<string> l = new List<string>();

            }

            sw.Stop();

            Console.WriteLine("CreateListNormally -> Time taken: {0}ms",

              sw.Elapsed.TotalMilliseconds);

        }

        public static void CreateListReflection()

        {

            Stopwatch sw = Stopwatch.StartNew();

            Type lType = typeof(List<int>);

            for (int i = 0; i <= 1000; i++)

            {

                var l = Activator.CreateInstance(lType);

            }

            sw.Stop();

            Console.WriteLine("CreateListReflection -> Time taken:

              {0}ms", sw.Elapsed.TotalMilliseconds);

        }

    }

}

This produces the output shown in Figure 22.

Output of the Speed Comparison with and without Reflection

Figure 22: Output of the Speed Comparison with and without Reflection

In this example, using Reflection takes 4.298 times longer than using standard code. The power of creating the needed Type during runtime using CreateInstance comes at the price of performance.

Let’s now examine the other big drawback of Reflection—consistency.

We cannot expect the same results from executing private methods and properties that we get with public methods and properties.

Code Listing 28 shows a library that we reference in our application. Let’s assume this is a vendor library that we have no control over but that we need in order to troubleshoot a specific problem our customer has raised.

The vendor has simply provided the assembly we can use to reference in our application.

Code Listing 28: Vendor Library Source Code

using System;

using System.Collections.Generic;

namespace ReflexionVendorSampleAssembly

{

    public class VendorAssembly

    {

        private const string cStr = "dd-MMM-yyyy hh:mm:ss UTC";

        private DateTime dtDate;

        private List<string> pItems;

        public string Info

        {

            get { return dtDate.ToString(cStr); }

        }

        public List<string> Items

        {

            get { return pItems; }

            set { pItems = value; }

        }

        public VendorAssembly()

        {

            pItems = new List<string>();

            AddNewItem();

        }

        public void AddNewItem()

        {

            dtDate = DateTime.UtcNow;

            pItems.Add(dtDate.ToString(cStr));

            Console.WriteLine("AddNewItem -> " +

              pItems.Count.ToString() + " - " + dtDate.ToString(cStr));

        }

        private void AddNewItemPriv()

        {

            Console.WriteLine("AddNewItemPriv -> " +

              pItems.Count.ToString() + " - " + dtDate.ToString(cStr));

        }

    }

}

Now let’s call this vendor library by standard means (assuming we have the source code) and by using Reflection (assuming we don’t have the source code).

Code Listing 29: Calling the Vendor Library with and without Reflection

using System.Diagnostics;

using System;

using System.Collections.Generic;

using ReflexionVendorSampleAssembly; //If we have the vendor’s code.

using System.Reflection;

namespace ReflectionExamples

{

    class Program

    {

        static void Main(string[] args)

        {

            VendorPublicItems();

            VendorPrivateItems();

            Console.ReadLine();

        }

        public static void VendorPublicItems()

        {

            Console.WriteLine("VendorPublicItems");

           

            VendorAssembly publicVendor = new VendorAssembly();

            publicVendor.AddNewItem();

        }

        public static void VendorPrivateItems()

        {

            Console.WriteLine("VendorPrivateItems");

            Type vendorType = typeof(VendorAssembly);

           

            var act = Activator.CreateInstance(vendorType);

            VendorAssembly pv = (VendorAssembly)act;

            MethodInfo dMet = vendorType.GetMethod("AddNewItemPriv",

              BindingFlags.NonPublic | BindingFlags.Instance);

            dMet.Invoke(pv, null);

        }

    }

}

With the VendorPublicItems method, we are assuming that we have the vendor’s library source code, which means we can create an instance of the VendorAssembly class, as usual, and then invoke the AddNewItem method.

With the VendorPrivateItems method, we assume that we do not have the vendor’s library source code, which means we must use Reflection to invoke the AddNewItemPriv method. In this particular example, we will explicitly call a private method within the vendor’s assembly to show that although this is technically possible, it is not necessarily a good practice.

The code execution of this example results in the output shown in Figure 23.

Output of the Vendor’s Library

Figure 23: Output of the Vendor’s Library

Let’s examine this closely.

When running VendorPublicItems, we see that AddNewItem is called by the constructor and invoked immediately after an instance of VendorAssembly has been created. This results in having two items on the VendorAssembly.Items list.

However, when VendorPrivateItems is called, only a single item is added to the VendorAssembly.Items list.

This happens because after calling CreateInstance, GetMethod is executed referencing AddNewItemPriv, which doesn’t actually add any element to the VendorAssembly.Items list.  Instead, it simply displays the item that already exists. 

Even though Reflection allows us to execute private methods within an assembly, we should avoid following this practice. Typically, private methods and properties do not execute the same logic as their public counterparts. They have been marked private for a good reason.

By executing a private method or property through Reflection (for an assembly for which we might not have the source code), we are not ensuring that our intended results will be the same as those we would get when executing a public counterpart method or property.

To illustrate this, we made it quite obvious in the vendor’s code that the logic within the AddNewItem and AddNewItemPriv would be different.

In a real-world scenario, you might not have the vendor’s library source code at hand, so as a rule of thumb, when using Reflection make sure to invoke public methods and properties. This will guarantee that you get the same results as when you have the source code at hand and instantiate the class normally (without using Reflection).

Let’s change the VendorPrivateItems method to invoke the public AddNewItem method.

Code Listing 30: VendorPrivateItems Adjusted to Invoke the AddNewItem Method

using System.Diagnostics;

using System;

using System.Collections.Generic;

using ReflexionVendorSampleAssembly; //If we have the vendor’s code.

using System.Reflection;

namespace ReflectionExamples

{

    class Program

    {

        static void Main(string[] args)

        {

            VendorPublicItems();

            VendorPrivateItems();

            Console.ReadLine();

        }

        public static void VendorPublicItems()

        {

            Console.WriteLine("VendorPublicItems");

           

            VendorAssembly publicVendor = new VendorAssembly();

            publicVendor.AddNewItem();

        }

        public static void VendorPrivateItems()

        {

            Console.WriteLine("VendorPrivateItems");

            Type vendorType = typeof(VendorAssembly);

           

            var act = Activator.CreateInstance(vendorType);

            VendorAssembly pv = (VendorAssembly)act;

            MethodInfo dMet = vendorType.GetMethod("AddNewItem",

              BindingFlags.Public | BindingFlags.Instance);

            dMet.Invoke(pv, null);

        }

    }

}

Notice that both methods VendorPrivateItems (using Reflection) and VendorPublicItems (without Reflection) produce exactly the same results.

Output of the Vendor’s Library after Adjusting

Figure 24: Output of the Vendor’s Library after Adjusting

The only actions taken were changing the name of the method being retrieved by GetMethod from AddNewItemPriv to AddNewItem and changing BindingFlags.NonPublic to BindingFlags.Public.

Reflection strategy

In the previous example, the main reason our results were not consistent is that we violated the principle of encapsulation.

Classes are black boxes that have clearly defined input exposed to the outside world (public methods and properties). They also have clearly defined output exposed through properties or method return values.

So long as we stick to the basic rules of encapsulation and use the exposed input and output of the class we are interacting with, we are fine. However, if we try to dig inside the box, we can run into unexpected behavior, and this is exactly what happened in the previous demo.

We should not peek inside the box. Instead, we should use the exposed interfaces, public methods, and properties. In Reflection, this means we should not interact with any nonpublic members of any class.

We also saw that our speed is reduced when we use Reflection. This can be mitigated by programming to an abstraction rather than a concrete type or, in other words, by writing your code to target an interface rather than using a specific class.

When your code works with interfaces through Reflection instead of the dynamically created type, performance will increase.

Here’s a strategy you should consider when working with Reflection—if you want the flexibility of choosing functionality at runtime, you should dynamically load assemblies only once (at application start-up). Again—make sure that the assembly is loaded just once. It is also important to dynamically load any types from those assemblies at start-up.

Most importantly, once you have a dynamically loaded type, this should be cast to an interface that your application already knows about. Doing this will ensure your application will make all method calls through the interface (not using MethodInfo.Invoke), which will help reduce the performance hit when using Reflection.

Since interfaces have only public members, you will avoid interacting with any private members of the class and also avoid experiencing unexpected behaviors from breaking encapsulation.

Table 7 summarizes the proposed Reflection strategy.

Table 7: Proposed Reflection Strategy

Strategic Item

Remark

Dynamically Load Assemblies

Once and during application start-up

Dynamically Load Types

Once and during application start-up

Cast Types to Known Interfaces

All method calls go through the interface

No usage of MethodInfo.Invoke

Stick to Encapsulation

Don’t interact with private members

Following this strategy will limit the use of Reflection to only what you specifically need. You will also maximize performance and accuracy while maintaining flexibility.

This is all valid for web (ASP.NET, MVC, and WebForms), console, and desktop (Windows Forms and WPF) applications written in C#. However, because Windows Store apps are sandboxed, Reflection features are limited.

There’s no access allowed to nonpublic methods or types (which is a good thing), and there’s no dynamic loading of assemblies available from the file system. This is because all the assemblies that a Windows Store application needs must be submitted as a package to the Windows Store.

Pluggable agents

Having seen what Reflection can do, let’s examine how we can make use of it in our applications following the strategy we have outlined.

Let’s say we are working with an application that browses specific job websites to get data on skills trends in the market. We won’t actually build the specific logic of gathering data from the websites, but we’ll show how we can create a pluggable architecture following the principles outlined in the proposed strategy.

To achieve this, an abstraction layer must be created, and it should consist of pluggable agents that adhere to a common interface. Each pluggable agent will gather information from a different job website, but all the plugins will share the same common abstraction in order to plug into the main system.

First, let’s create the interface. We will keep it simple and use only a method that all plugins should implement called NavigateToSite.

Code Listing 31: Plugin Interface

namespace TrendsAgent

{

    public interface IPluginInterface

    {

        string[] NavigateToSite(string url);

    }

}

This method, which will be implemented by all pluggable agents, will simply receive as a parameter the URL of the website and return a string array with all the HTML lines of the webpage retrieved.

With this interface defined, we’ll use GetType(string typeName) from the Type class, where typeName is the assembly-qualified type name, to properly implement our Reflection strategy.

Once we have the Type object, we can use the CreateInstance(Type type) method of the Activator class to create an instance of a Type that was obtained with GetType(string typeName).

The assembly-qualified type name contains five parts:

  • Fully-qualified type name (namespace and type).
  • Assembly name.
  • Assembly version.
  • Assembly culture.
  • Assembly public (for strongly-named assemblies).

The fact that .NET allows us to specify an assembly version and culture is great because this allows us to use different versions of the assembly or a version specific to a certain locale (great when localization is required) during run time.

Limiting the use of Reflection to loading and creating the pluggable agents will make for better performance, however we will not use Reflection to dynamically invoke members. Instead, we’ll take the instance created and interact with it using the IPluginInterface. By interacting with the interface, we’ll be able to directly call the NavigateToSite method.

In order to have full separation of concerns (so we can load the pluggable agent’s assembly from the main application using Reflection), the agent’s code should be defined in a separate Visual Studio Project and not in the one used for the IPluginInterface.

The Visual Studio project that contains IPluginInterface is the host application that will load the pluggable agent.

Let’s jump into some code and see how we can implement a pluggable agent.

Code Listing 32: Pluggable Agent

using System.Net;

using TrendsAgent;

namespace TrendsPlugin

{

    public class PluginDemo : IPluginInterface

    {

        public string[] NavigateToSite(string url)

        {

            string[] p = null;

            using (var client = new WebClient())

            {

                client.Headers["User-Agent"] =

                    "Mozilla/4.0 (Compatible; Windows NT 5.1; MSIE 6.0) "

                    +

                    "(compatible; MSIE 6.0; Windows NT 5.1; " +

                    ".NET CLR 1.1.4322; .NET CLR 2.0.50727)";

                using (var stream = client.OpenRead(url))

                {

                    string value = client.DownloadString(url);

                    p = value.Split('\r');

                }

            }

            return p;

        }

    }

}

Here you can see how the PluginDemo class from TrendsPlugin implements the IPluginInterface from the host program.

In this PluginDemo class, NavigateToSite fetches the HTML content of the given URL and returns it as a string array.

Now let’s use limited Reflection from the main program to examine the how the pluggable agent PluginDemo is called.

Code Listing 33: Using Reflection to Invoke the Pluggable Agent from the Main Method

using System;

namespace TrendsAgent

{

    class Program

    {

        static void Main(string[] args)

        {

            LoadRunPlugin();

            Console.ReadLine();

        }

        public static void LoadRunPlugin()

        {

            string pluginName = "TrendsPlugin.PluginDemo, TrendsPlugin,

            Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";

            Type pluginType = Type.GetType(pluginName);

            object pluginInstance = Activator.CreateInstance(pluginType);

            IPluginInterface plugin = pluginInstance as IPluginInterface;

            string[] l = plugin.NavigateToSite("http://google.com");

            Console.WriteLine("Lines fetched: " + l.Length.ToString());

        }

    }

}

Let’s use the assembly-qualified type name TrendsPlugin.PluginDemo, TrendsPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null to take a closer look.

We are able to load the assembly’s type into the main program (using the LoadRunPlugin method). However, for this to happen, the TrendsPlugin.dll (which contains the PluginDemo class) must be in the same folder from which the main program is executed.

An easy way to get the assembly-qualified type name is to open the TrendsPlugin.dll in ILSpy. This is clearly visible at the top of the ILSpy screen in green.

Using ILSpy to Get the Assembly-Qualified Type Name

Figure 25: Using ILSpy to Get the Assembly-Qualified Type Name

Having pluginType, next we create an instance of it using Activator.CreateInstance. This will return an object that can be cast into an IPluginInterface instance on which NavigateToSite can be invoked.

By doing this, we are able to use minimal features from Reflection, adhere to the correct strategy, and follow the rules of encapsulation in order to load and execute the pluggable agent.

Best practices

We’ve seen how we can use Reflection to inspect assemblies and how to use it to invoke members of classes for which we might not have the source code.

In essence, the purpose of using Reflection in this e-book is to simply illustrate the power this technology has and how it can enable us to be more productive when troubleshooting—especially when dealing with third-party libraries.

It is true that Reflection is widely used, especially for the creation of developments tools (ILSpy is a great example of such a tool).

So, is .NET code really safe?

Frankly it’s not much different from other compiled languages. If someone has access to the compiled file, this can be reversed using Reflection or any of the tools available in the market, including .NET Reflector, dotPeek, ILDASM, JustDecompile, Moq, Unity, and ILSpy.

There are many other similar tools for both Java applications and disassemblers for native Win32/64 executables. For example: Procyon, Krakatau, and CFT for Java, and Boomerang, ExeToC, and IDA for Win32/64 executables.

If the files are on the user’s machine, they can be reversed and examined. However, this doesn’t mean we are hopeless. There are several things that can be done to minimize the risk.

Let’s explore some ways in which this can be done.

SOA

Limiting what actually runs on the client machine and keeping proprietary code (which gives us a competitive business advantage) on the server are two ways of minimizing risk.

Table 8: Client versus Server Execution

Client Machine

Server

Nonsecret code runs here

Proprietary code runs here

Calls server for secrets

Returns results to the client

When the client requests the server for the information it needs, there should be some authentication process built into the server’s code that verifies that the request is coming from a valid client machine.

Once this verification takes place, the server will return the results to the client. These should be encrypted and returned. This very common architecture is known as service-oriented architecture (SOA), which you might have heard about.

Running a server service is an integral part of component thinking, and it is clear that distributed architectures were early attempts to implement service-oriented architecture.

Most of the time, web services are mistaken as the same thing as SOA, but web services are part of the wider picture that is SOA.

Web services provide us with certain architectural characteristics and benefits—specifically platform independence, loose coupling, self-description, and discovery—and they can enable a formal separation between the provider and consumer because of the formality of the interface.

Service is the important concept. Web services are the set of protocols by which services can be published, discovered, and used in a technology-neutral and standard way.

Web services are not necessarily a compulsory component of an SOA, although increasingly they have become so. SOA is much wider in its scope than simply defining service implementations. It addresses the quality of the service from the perspective of the provider (server) and the consumer (client application).

Table 9: SOA versus Web Services

SOA

Web Services

Service is abstracted from implementation

Service is consumable and discoverable

Published service interface

Use of the service itself—not copied from the server to the client

Contract between the consumer and provider

Endpoint and platform independent

A well-formed service provides us with a unit of management that relates to business usage.

The client machine requests what it needs from the server, providing us with a basis for understanding the lifecycle costs of the service and how the lifecycle is used within the business.

With this information, we can design better applications, and we can design them to be better aligned with the realities and expectations of businesses.

Secrets

When an assembly is opened with tool like ILSpy, any string contained within the assembly is easily revealed.

Neither passwords nor sensitive strings should be hard coded. You can avoid this by using a password linked to a user’s authentication. However, this is not always possible or practical.

Another option is to have the client application request the application token from the server or have it run the code that requires the password on the server.

In an absolute worst-case scenario in which a password or token needs to be stored on the client application, it must be obfuscated to the extent that it cannot be exposed when the code is decompiled.

Obfuscation

Obfuscation is a way to make CIL code less readable. We cannot prevent Reflection; however, we can rename private variables, fields, properties, parameters, methods, and constants to nonsense names that will make the code harder to read when decompiled.

The code still continues to work, but it will be harder for human beings to understand. When obfuscation takes place, it might be possible that Reflection won’t work anymore on private members (decompilers cannot follow the code). However, public member names usually remain unchanged.

In a way, obfuscation is similar to JavaScript minification. The main difference is that minification’s purpose is for faster downloading of a JavaScript library to the browser. Minification reduces the download payload by removing all unnecessary characters in the JavaScript code.

There are also some advanced obfuscators that go a step further and try to prevent any disassembly. This is typically done by using unprintable characters (which is still valid CIL) when renaming private variables, fields, properties, parameters, methods, and constants to nonsense names.

Keep in mind that ultimately this is an ongoing battle between obfuscators and decompilers. As one evolves, so does the other.

Some well-known obfuscators include Dotfuscator (a free version with fairly limited functionality ships with Visual Studio) and two commercial products—Eazfuscator and Deep Sea Obfuscator. There are many more.

Obfuscation is a very broad topic that we won’t cover in this e-book, but reviewing and understanding it is certainly worthwhile.

Summary

Throughout this chapter we’ve explored how Reflection can help us understand code to which we might not have access.

Sometimes, when trying to replicate production problems or trying to analyze software issues, we might be faced with the challenge of understanding third-party libraries used by our application. This is where Reflection can give us a hand.

In this e-book, we’ve turned upside down the notion of what a helpdesk at a software company might look like. We’ve also set forth guidelines to increase customer loyalty and awareness.

Here are the key reasons why customer loyalty is so important:

Incrementing Referrals: The customer lifecycle has become more complex because customers begin their buying journey even before they directly engage with your product and brand.

Therefore, customer-relationship management should begin at the very first contact. This is why having an embedded Simple Awesome CRM Tool within your product can be invaluable as a strategy.

Reduced Costs: By cultivating customer loyalty, your business can create a legion of strong evangelists who outshine your best marketing efforts. These evangelists can help save huge advertising and marketing costs for your company because word of mouth is a powerful and cost-effective marketing tool.

Furthermore, customer retention efforts are cheaper than acquiring new customers. Building loyalty forms a solid customer base that helps your business grow, potentially even exponentially.

Repeat Business: Because your organization might have already won the trust of loyal customers by applying these techniques, it is easier to cross-sell and up-sell to these customers compared to new ones.

If you maintain an open line of communication with loyal customers, they will generally provide honest feedback to help improve your brand. And because they will openly voice their comments, your team can make a concerted effort to address issues raised promptly, which will increase customer satisfaction.

Loyal customers are likely to ensure repeated business, which is critical for your company’s long-term survival. 

Insulation from Competitors: Loyal customers will stick to your brand because they trust you.

If your organization has a loyal customer base, you should be fairly immune to both competition and economic changes. This helps to protect your bottom line.

Customer loyalty strengthens your product and brand image, and it builds equity. A truly positive reputation can help your business scale to greater heights. Your team should focus on treating customers better and rewarding them for their loyalty. After all, customers are the biggest assets your business can have.

The code samples presented in this e-book were created with Visual Studio 2015 and .NET 4.5.2 and can be found here:

https://bitbucket.org/syncfusiontech/customer-success-for-c-developers-succinctly  

I hope this e-book has inspired you to start a journey to achieving greater customer success.

Thank you.

 

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.